├── .cargo └── config.toml ├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-push ├── .nvmrc ├── .proxyrc.json ├── .sqlx ├── query-0121880afb20a899856cb784e752561924a99b073e6fba4e669257b3a4c15ff1.json ├── query-0198d93680a7781f92e2740c73703404ac24cb09cf70c18ff3297a6524cb861d.json ├── query-06a96093a952ca7b5d28c78a2ffc59731fd279308f8acfe505b21f867048aefe.json ├── query-06b024f294f9626f281b064c118b4a192a510610f08d997d3f2a756491446158.json ├── query-075beb11d14dce16dcb26fa0e21dfcbe51f8df9501773dd3b4bf0aa31ecdb50d.json ├── query-08324f9b9c086bde12d5a6ce2c3d1b7c8d6b02e2a6b5bbc3c0776dadf9e2efde.json ├── query-0a17529a8109d351c0353efa1332b5bc275645af78f33fec5ce0e2039a033f10.json ├── query-0dc7a87ffa2ec4c15a5bc653b9719a304127581d930d47d53b7cd8f333f5cf35.json ├── query-1695ab79095c95a7b618d5a2bcadb224a79ee2ede903b4924fd3f5539e1b6171.json ├── query-2184c690e1b380b7f8b8aa237c495b09b23b7286e5753183015be3cdb021826e.json ├── query-236117c8d13b7643a38ecc1a87fedb730d16aad41adf8bc1a8e30fbd4b46ea92.json ├── query-250c5bc39175d97b7a59662d866353520fe00b1161f88454d2478fca612e4197.json ├── query-25dd81897ddf37b727a99f4048aefda36257f0697b8de4c08b182c3486f1c8e5.json ├── query-27ab8c3e799763d51be0bedf5163bf496a6e24615fd8fb12a5a841645034839b.json ├── query-2808bb0b0fcbe605fd8c4033b5b66bfb99905906782e93f462724553646c3b45.json ├── query-2c0b85fd43a4c41034dc025ea26e6f63337cd20e9fdc2f99457de7f7214d5f0c.json ├── query-3224d177c41738db1c75252a7183dc9f5bfd376dc90b696ac40e99798225417b.json ├── query-332b530d376ec5f092d2a12ec371bf9f49547575bae1e7f11fc6ce5d39308523.json ├── query-3bb01e88f57c80a3537d7bf39fd9962592802ea58eed0136c8b655610bc7f919.json ├── query-47b3211344c71c951ab4bb8e71bacd47fc54001e7d3a60796039454404011fa1.json ├── query-4f33d79bf3c4684dd292b491f1ec550261df7ebbd2bf2d6f904a1f9749fd5a80.json ├── query-61a42354728ee9fffa05bb0b70b1aa5b056d9150c63f7f41aa51d36b2c9734fa.json ├── query-6fe21866a7712ca5cea225dd4a6838377718a153f5b085191e1906814261423d.json ├── query-7160231168e9996b47c86ee837300a5dba4b42595ebc97cdaa028ad11055432d.json ├── query-888b251abf2d1f82c2bec9dc07bb172797fb301bca932635f1ed398c73e8bd92.json ├── query-8e2bdeabde8cca09128f974ab6560c9050811aa6f9e0b8d96f37371dde6549eb.json ├── query-9347d958c7d809bf925cc591b552167823cb45ee16412895ce0148080c210c00.json ├── query-9349bd205fb7e6a1f84958ade34112ee576e1854545289766871e46180f99570.json ├── query-98523a4ee01682cdc05ddeb9a624b4a3c14ff1b80e59f6ae9e759af2de97fd6c.json ├── query-9b79ee2289fdc7c259a6466bca1e97d264e4a965093244c41d4f2517963a9384.json ├── query-a231b8dc4515d04e7e0021ecc27cf83e6263521afc7ae5a06faf7b5a5c876ce7.json ├── query-a2f418038dacb4e9089d70b7168058f9e1a663daf37de4cd5e5450810f18d440.json ├── query-a7fedd16f798de891e278fb9ee10064368010a6fc5f4399b0e60bb860f8d1222.json ├── query-a807490d7d3eaad38f8ac404bdef695fc6d7d8e5babec9fec19e742fd3cb5aff.json ├── query-a88d81a3637a342d885e39262da39ee50ce9695b3f520f4d61e063d8559c686e.json ├── query-a9f9ecff772fe17c0a99cfa86c01572cdfd8d6c4058cab7e3d9af84b17e5d3c5.json ├── query-ae246ca497b982b978aa9be511679f2a8791a662b0eb85085592c791f18b881f.json ├── query-aef3a29ec6dfaa83bad8edb946c7ef9f25b5a13e10567f287a5d7275d1ac8e1e.json ├── query-b8c1d7fb1b4e0572abe4810e77361ee603c2937d643a47e268da19fa56ea0be9.json ├── query-be55e9297145f15ad560a3193a9f80184b8cd3824e8feab61ad2017528cecf7b.json ├── query-bf3ca443d10c22de3e0db2abe082669b2fdf999b34748f456f8045e8124fa816.json ├── query-c0e494494848b3db7599a64d0012b403a8081df37b2f285fa1c5a417c543f8c5.json ├── query-c91a9e3f7932e6ce435499aade8500aa507f4007f275a80d907ac20ae6e13526.json ├── query-cd3bebc681cf49b4fd801f5132be7930053baecafe364fdd33c70ad55690b63f.json ├── query-cef012896306769cfa83066194ef4e6f992c4e57c12457cb0b4e8e75c37fef0e.json ├── query-d3d1847929178cd92eb4f44fd6bff280eececef4b45a8792da59a2ed7e3ece1b.json ├── query-d3dff488871283501e349cd285509197be3ab78d76fea4127246bee48a454dbe.json ├── query-d4401c53e62182e9edefb08e8e983b76e3360b6d982eb356aa0f78b84e4f9403.json ├── query-d76c9d4c1ec5d3d058cfc60977a8a91878a878d716da8e6052a464689705d6c4.json ├── query-db62941637a00720920aefdbf9b2a84ec1b3191a7c4b73df4144e9f0067d43f4.json ├── query-dc139034ac870e4570fabdd382f5d6966ad5f1590f5e714e2bade89dda8a67f8.json ├── query-dcb605cadaeb5e99f0ce5351e64c0da818f3b818ab2693f1ea6f3d1f134a253c.json ├── query-ddbae2f82f3d5bb5f89536340277b6c90c00ddf699c52657bb56ea84ad3d5166.json ├── query-de8d14405e97872fa7ec2860dea97980afd3b300b83edc495a2cd7e29eb3fec4.json ├── query-e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7.json ├── query-e2b703445a00437ce922f2e3c134330ee11d6cc70b46338eec5cd8d11e509c6e.json ├── query-e55f4b70c7e02ab79d8edf359d7f9887105c9955e01a3c132e7720895fcfd2b0.json ├── query-e75a0d9e59f1f97ccb66410d0e4ef9db4d474729e3cd9e6bd277c1cf72ed1aef.json ├── query-ebf1133ee28bcc9f96055d4011c488493a1c384ea5d6ed22d732cecedf20242e.json ├── query-eefc34b063aaae0f6b7c2ae9a08879234871dae520a60c3007dcc2eea2322aa8.json ├── query-f29c17461e72704ead846db2b7bce65d28b747eb6af9adf28b843cf21ec4514b.json ├── query-fa59a4e24dfaeaeb848f9e4f7759f63caa970476e4b4379cf71f0d5f133b3fa3.json └── query-fbdeb3d83769b69a82a5207f271a46a6a3a78158d2e0a253f5026b1ac82bb83d.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Dockerfile.aarch64-unknown-linux-musl ├── Dockerfile.dockerignore ├── Dockerfile.docs ├── Dockerfile.webui ├── Dockerfile.webui.dockerignore ├── LICENSE ├── README.md ├── SECURITY.md ├── assets ├── logo │ ├── secutils-logo-initials-source.svg │ ├── secutils-logo-initials.png │ ├── secutils-logo-initials.svg │ ├── secutils-logo-source.svg │ ├── secutils-logo-with-text-source.svg │ ├── secutils-logo-with-text.png │ └── secutils-logo-with-text.svg └── templates │ ├── account_activation_email.hbs │ ├── account_recovery_email.hbs │ ├── email_styles.hbs │ ├── web_page_content_tracker_changes_email.hbs │ ├── web_page_content_tracker_changes_error_email.hbs │ ├── web_page_resources_tracker_changes_email.hbs │ └── web_page_resources_tracker_changes_error_email.hbs ├── build.rs ├── components ├── secutils-docs │ ├── README.md │ ├── babel.config.js │ ├── blog │ │ ├── 2023-05-19-beta-release.md │ │ ├── 2023-05-25-technology-stack-overview.md │ │ ├── 2023-05-28-deployment-overview.md │ │ ├── 2023-05-30-usage-analytics-and-monitoring.md │ │ ├── 2023-06-01-running-micro-saas-for-less-than-one-euro-a-month.md │ │ ├── 2023-06-06-project-management.md │ │ ├── 2023-06-08-security-configuration-management.md │ │ ├── 2023-06-13-project-finances.md │ │ ├── 2023-06-15-q2-2023-update-resources-tracker.md │ │ ├── 2023-06-20-why-i-started-writing-regularly.md │ │ ├── 2023-06-23-exploring-services-with-webhooks.md │ │ ├── 2023-06-27-time-management.md │ │ ├── 2023-06-30-ai-integration.md │ │ ├── 2023-07-04-negative-user-feedback.md │ │ ├── 2023-07-11-detecting-changes-in-js-css-part-1.md │ │ ├── 2023-07-13-detecting-changes-in-js-css-part-2.md │ │ ├── 2023-07-18-detecting-changes-in-js-css-part-3.md │ │ ├── 2023-07-20-2-months-of-building-in-public.md │ │ ├── 2023-07-25-alpha2-release.md │ │ ├── 2023-07-27-tiny-fix-big-impact-high-risk.md │ │ ├── 2023-08-01-q3-2023-iteration.md │ │ ├── 2023-08-08-scheduler-component.md │ │ ├── 2023-08-15-false-positives-part-1-small-apps.md │ │ ├── 2023-08-17-false-positives-part-2-large-apps.md │ │ ├── 2023-08-22-useful-newsletters-and-podcasts.md │ │ ├── 2023-08-29-best-application-security-tool-is-education.md │ │ ├── 2023-09-05-q3-2023-update-notifications.md │ │ ├── 2023-09-12-running-web-scraping-service-securely.md │ │ ├── 2023-10-04-alpha3-release.md │ │ ├── 2023-10-10-q4-2023-iteration.md │ │ ├── 2023-11-07-two-simples-rules-for-secure-code.md │ │ ├── 2023-11-28-explore-websites-through-csp.md │ │ ├── 2024-01-16-web-page-content-trackers-and-playwright.md │ │ ├── 2024-01-24-rust-application-with-js-extensions.md │ │ ├── 2024-02-20-security-mindset.md │ │ ├── 2024-06-11-open-source-intelligence-grafana.md │ │ └── authors.yml │ ├── config │ │ └── nginx.conf │ ├── docs │ │ ├── guides │ │ │ ├── _category_.json │ │ │ ├── digital_certificates │ │ │ │ ├── _category_.json │ │ │ │ ├── certificate_templates.md │ │ │ │ └── private_keys.md │ │ │ ├── web_scraping │ │ │ │ ├── _category_.json │ │ │ │ ├── content.md │ │ │ │ └── resources.md │ │ │ ├── web_security │ │ │ │ ├── _category_.json │ │ │ │ └── csp.md │ │ │ └── webhooks.md │ │ └── project │ │ │ ├── _category_.json │ │ │ ├── changelog │ │ │ ├── 2023.md │ │ │ ├── 2024.md │ │ │ └── _category_.json │ │ │ ├── intro.md │ │ │ └── roadmap.md │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src │ │ └── scss │ │ │ └── index.scss │ ├── static │ │ ├── img │ │ │ ├── blog │ │ │ │ ├── 2023-06-06_breakdown.png │ │ │ │ ├── 2023-06-06_notion.png │ │ │ │ ├── 2023-06-06_roadmap.png │ │ │ │ ├── 2023-06-08_csp_create.png │ │ │ │ ├── 2023-06-08_csp_deploy.png │ │ │ │ ├── 2023-06-08_csp_monitor.png │ │ │ │ ├── 2023-06-08_mdn_csp.png │ │ │ │ ├── 2023-06-13_portfolio.png │ │ │ │ ├── 2023-06-15_resources_trackers.png │ │ │ │ ├── 2023-06-20_readers_stat.png │ │ │ │ ├── 2023-06-23_web_bookmark.png │ │ │ │ ├── 2023-06-23_webhook_v1.png │ │ │ │ ├── 2023-06-23_webhook_v1_requests.png │ │ │ │ ├── 2023-06-23_webhook_v2.png │ │ │ │ ├── 2023-06-23_webhook_v3_headers.png │ │ │ │ ├── 2023-06-23_webhook_v3_iframe.png │ │ │ │ ├── 2023-06-27_time_management.png │ │ │ │ ├── 2023-06-30_auto_responders_chat_gpt.png │ │ │ │ ├── 2023-07-11_web_page_weight.png │ │ │ │ ├── 2023-07-13_web_page_resources.png │ │ │ │ ├── 2023-07-18_web_page_resources_tracker.png │ │ │ │ ├── 2023-07-20_dara_knot.png │ │ │ │ ├── 2023-07-27_phishing.png │ │ │ │ ├── 2023-08-01_q3_2023_iteration.png │ │ │ │ ├── 2023-08-08_scheduler_component_job_activity.png │ │ │ │ ├── 2023-08-08_scheduler_component_job_create.png │ │ │ │ ├── 2023-08-15_cost_of_false_positives.png │ │ │ │ ├── 2023-08-17_cost_of_false_positives_large_apps.png │ │ │ │ ├── 2023-08-22_useful_newsletters_and_podcasts.png │ │ │ │ ├── 2023-08-29_best_application_security_tool_is_education.png │ │ │ │ ├── 2023-09-05_q3_2023_update_notifications.png │ │ │ │ ├── 2023-09-12_running_web_scraping_service_securely.png │ │ │ │ ├── 2023-10-04_custom_resources_filtering.png │ │ │ │ ├── 2023-10-04_email_notifications.png │ │ │ │ ├── 2023-10-04_resources_trackers_enhancements.png │ │ │ │ ├── 2023-10-04_scheduled_resource_checks.png │ │ │ │ ├── 2023-10-04_sharing.png │ │ │ │ ├── 2023-10-10_subdomain_responders.png │ │ │ │ ├── 2023-11-07_easy_rules_secure_code.png │ │ │ │ ├── 2023-11-28_import_policy_bing.png │ │ │ │ ├── 2023-11-28_import_policy_chatgpt.png │ │ │ │ ├── 2023-11-28_import_policy_chatgpt_policy.png │ │ │ │ ├── 2023-11-28_import_policy_chatgpt_reporting.png │ │ │ │ ├── 2023-11-28_import_policy_dialog.png │ │ │ │ ├── 2023-11-28_import_policy_duckduckgo.png │ │ │ │ ├── 2023-11-28_import_policy_google.png │ │ │ │ ├── 2024-01-16_web_page_content_tracker.png │ │ │ │ ├── 2024-01-16_web_page_content_tracker_preview.png │ │ │ │ ├── 2024-01-16_web_page_content_tracker_ui.png │ │ │ │ ├── 2024-01-24_rust_application_with_js_extensions_execution_time.png │ │ │ │ ├── 2024-01-24_rust_application_with_js_extensions_memory.png │ │ │ │ ├── 2024-01-24_rust_application_with_js_extensions_terminations.png │ │ │ │ ├── 2024-02-20_security_mindset.png │ │ │ │ ├── 2024-02-20_security_mindset_always_be_learning.png │ │ │ │ ├── 2024-02-20_security_mindset_assume_compromise.png │ │ │ │ ├── 2024-02-20_security_mindset_avoid_insecurity_through_obscurity.png │ │ │ │ ├── 2024-02-20_security_mindset_be_pragmatic_about_security.png │ │ │ │ ├── 2024-02-20_security_mindset_explore_unhappy_path.png │ │ │ │ ├── 2024-02-20_security_mindset_logs_and_exceptions.png │ │ │ │ ├── 2024-06-11_open_source_intelligence_grafana.png │ │ │ │ ├── 2024-06-11_open_source_intelligence_grafana_codeowners.png │ │ │ │ ├── 2024-06-11_open_source_intelligence_grafana_codeowners_with_commits.png │ │ │ │ ├── 2024-06-11_open_source_intelligence_grafana_linkedin.png │ │ │ │ ├── elastic.png │ │ │ │ ├── goal.png │ │ │ │ └── plausible.png │ │ │ ├── docs │ │ │ │ ├── changelog_1.0.0_alpha.3_certificates_curve_name.png │ │ │ │ ├── changelog_1.0.0_alpha.3_certificates_key_size.png │ │ │ │ ├── changelog_1.0.0_alpha.3_sharing.png │ │ │ │ ├── changelog_1.0.0_alpha.3_web_scraping.png │ │ │ │ ├── changelog_1.0.0_alpha.4_content_trackers.png │ │ │ │ ├── changelog_1.0.0_alpha.4_import_csp.png │ │ │ │ ├── changelog_1.0.0_alpha.4_private_keys.png │ │ │ │ ├── changelog_1.0.0_alpha.4_responders_same_path.png │ │ │ │ ├── changelog_1.0.0_alpha.4_responders_subdomain.png │ │ │ │ ├── changelog_1.0.0_alpha.4_retries.png │ │ │ │ ├── changelog_1.0.0_alpha.4_share_certificate_templates.png │ │ │ │ ├── changelog_1.0.0_alpha.4_tracker_headers.png │ │ │ │ ├── changelog_1.0.0_alpha.4_trackers_indicators.png │ │ │ │ ├── changelog_1.0.0_alpha.4_trackers_preview.png │ │ │ │ ├── changelog_1.0.0_alpha.5_responders_client_socket_and_query_string.png │ │ │ │ ├── changelog_1.0.0_alpha.5_responders_script.png │ │ │ │ ├── changelog_1.0.0_alpha.5_responders_script_indicator.png │ │ │ │ ├── changelog_1.0.0_beta.1_platform_account_management.png │ │ │ │ ├── changelog_1.0.0_beta.1_responders_enable.png │ │ │ │ └── guides_custom_tracker_schedule.png │ │ │ ├── favicon.ico │ │ │ ├── logo.svg │ │ │ └── logo_dark.svg │ │ └── video │ │ │ ├── blog │ │ │ ├── 2023-06-30_auto_responders_chat_gpt.mp4 │ │ │ └── 2023-06-30_auto_responders_chat_gpt.webm │ │ │ └── guides │ │ │ ├── digital_certificates_certificate_templates_https_server.mp4 │ │ │ ├── digital_certificates_certificate_templates_https_server.webm │ │ │ ├── digital_certificates_certificate_templates_jwk_export.mp4 │ │ │ ├── digital_certificates_certificate_templates_jwk_export.webm │ │ │ ├── digital_certificates_certificate_templates_template_share.mp4 │ │ │ ├── digital_certificates_certificate_templates_template_share.webm │ │ │ ├── digital_certificates_private_keys_ecdsa.mp4 │ │ │ ├── digital_certificates_private_keys_ecdsa.webm │ │ │ ├── digital_certificates_private_keys_rsa.mp4 │ │ │ ├── digital_certificates_private_keys_rsa.webm │ │ │ ├── web_scraping_content_tracker.mp4 │ │ │ ├── web_scraping_content_tracker.webm │ │ │ ├── web_scraping_content_tracker_diff.mp4 │ │ │ ├── web_scraping_content_tracker_diff.webm │ │ │ ├── web_scraping_resources_tracker.mp4 │ │ │ ├── web_scraping_resources_tracker.webm │ │ │ ├── web_scraping_resources_tracker_diff.mp4 │ │ │ ├── web_scraping_resources_tracker_diff.webm │ │ │ ├── web_scraping_resources_tracker_filter.mp4 │ │ │ ├── web_scraping_resources_tracker_filter.webm │ │ │ ├── web_security_csp_import_policy_string.mp4 │ │ │ ├── web_security_csp_import_policy_string.webm │ │ │ ├── web_security_csp_import_policy_url.mp4 │ │ │ ├── web_security_csp_import_policy_url.webm │ │ │ ├── web_security_csp_new_policy.mp4 │ │ │ ├── web_security_csp_new_policy.webm │ │ │ ├── web_security_csp_policy_share.mp4 │ │ │ ├── web_security_csp_policy_share.webm │ │ │ ├── web_security_csp_report_policy_violations.mp4 │ │ │ ├── web_security_csp_report_policy_violations.webm │ │ │ ├── web_security_csp_test_policy.mp4 │ │ │ ├── web_security_csp_test_policy.webm │ │ │ ├── webhooks_dynamic_responder.mp4 │ │ │ ├── webhooks_dynamic_responder.webm │ │ │ ├── webhooks_html_responder.mp4 │ │ │ ├── webhooks_html_responder.webm │ │ │ ├── webhooks_json_responder.mp4 │ │ │ ├── webhooks_json_responder.webm │ │ │ ├── webhooks_tracking_responder.mp4 │ │ │ └── webhooks_tracking_responder.webm │ └── tsconfig.json ├── secutils-jwt-tools │ ├── Cargo.toml │ └── src │ │ └── main.rs └── secutils-webui │ ├── .parcelrc │ ├── .prettierrc.json │ ├── README.md │ ├── config │ └── nginx.conf │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ ├── app_container │ │ ├── app_container.tsx │ │ ├── app_context.ts │ │ ├── contact_form_modal.tsx │ │ ├── index.ts │ │ └── settings_flyout.tsx │ ├── assets.d.ts │ ├── assets │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ ├── components │ │ ├── index.ts │ │ ├── logo.tsx │ │ ├── logo_with_name.tsx │ │ ├── page_error_state.tsx │ │ ├── page_loading_state.tsx │ │ ├── page_state.tsx │ │ ├── page_success_state.tsx │ │ └── page_under_construction_state.tsx │ ├── favicon.ico │ ├── hooks │ │ ├── index.ts │ │ ├── page_header_actions.tsx │ │ ├── use_app_context.ts │ │ ├── use_local_storage.ts │ │ ├── use_page_meta.ts │ │ └── use_range_ticks.ts │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── model │ │ ├── async_data.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── search_item.ts │ │ ├── security_flows.ts │ │ ├── server_status.ts │ │ ├── ui_state.ts │ │ ├── urls.ts │ │ ├── user.ts │ │ ├── user_settings.ts │ │ ├── user_share.ts │ │ ├── user_subscription.ts │ │ ├── util.ts │ │ └── webauthn.ts │ ├── pages │ │ ├── activate │ │ │ ├── activate_page.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── page.tsx │ │ ├── page_header.tsx │ │ ├── signin │ │ │ ├── confirm_access_modal.tsx │ │ │ ├── index.ts │ │ │ ├── recover_account_modal.tsx │ │ │ └── signin_page.tsx │ │ ├── signup │ │ │ ├── index.ts │ │ │ └── signup_page.tsx │ │ └── workspace │ │ │ ├── components │ │ │ ├── editor_flyout.tsx │ │ │ ├── help_page_content.tsx │ │ │ ├── script_editor.tsx │ │ │ ├── site_search_bar.tsx │ │ │ ├── styles.ts │ │ │ └── timestamp_table_cell.tsx │ │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── use_font_sizes.ts │ │ │ ├── use_scroll_to_hash.ts │ │ │ └── use_workspace_context.ts │ │ │ ├── index.ts │ │ │ ├── utils │ │ │ ├── certificates │ │ │ │ ├── certificate_attributes.ts │ │ │ │ ├── certificate_lifetime_calendar.tsx │ │ │ │ ├── certificate_template.ts │ │ │ │ ├── certificate_template_form.tsx │ │ │ │ ├── certificate_template_generate_modal.tsx │ │ │ │ ├── certificate_template_share_modal.tsx │ │ │ │ ├── certificates_certificate_templates.tsx │ │ │ │ ├── certificates_private_keys.tsx │ │ │ │ ├── consts.ts │ │ │ │ ├── encryption_mode.ts │ │ │ │ ├── encryption_mode_selector.tsx │ │ │ │ ├── private_key.ts │ │ │ │ ├── private_key_alg.ts │ │ │ │ ├── private_key_export_modal.tsx │ │ │ │ ├── save_certificate_template_flyout.tsx │ │ │ │ ├── save_private_key_flyout.tsx │ │ │ │ └── shared_certificate_template.tsx │ │ │ ├── home │ │ │ │ └── home.tsx │ │ │ ├── index.ts │ │ │ ├── web_scraping │ │ │ │ ├── consts.ts │ │ │ │ ├── web_page_content_tracker_edit_flyout.tsx │ │ │ │ ├── web_page_content_tracker_revision.tsx │ │ │ │ ├── web_page_content_trackers.tsx │ │ │ │ ├── web_page_data_revision.ts │ │ │ │ ├── web_page_resource.ts │ │ │ │ ├── web_page_resources_tracker_edit_flyout.tsx │ │ │ │ ├── web_page_resources_tracker_revision.tsx │ │ │ │ ├── web_page_resources_trackers.tsx │ │ │ │ ├── web_page_tracker.ts │ │ │ │ ├── web_page_tracker_history.tsx │ │ │ │ ├── web_page_tracker_job_schedule.tsx │ │ │ │ ├── web_page_tracker_name.tsx │ │ │ │ └── web_page_tracker_retry_strategy.tsx │ │ │ ├── web_security │ │ │ │ └── csp │ │ │ │ │ ├── content_security_policy.ts │ │ │ │ │ ├── content_security_policy_copy_modal.tsx │ │ │ │ │ ├── content_security_policy_form.tsx │ │ │ │ │ ├── content_security_policy_import_modal.tsx │ │ │ │ │ ├── content_security_policy_sandbox_combobox.tsx │ │ │ │ │ ├── content_security_policy_share_modal.tsx │ │ │ │ │ ├── content_security_policy_sources_combobox.tsx │ │ │ │ │ ├── content_security_policy_trusted_types_combobox.tsx │ │ │ │ │ ├── save_content_security_policy_flyout.tsx │ │ │ │ │ ├── web_security_csp_policies.tsx │ │ │ │ │ └── web_security_csp_shared_policy.tsx │ │ │ └── webhooks │ │ │ │ ├── responder.ts │ │ │ │ ├── responder_edit_flyout.tsx │ │ │ │ ├── responder_name.tsx │ │ │ │ ├── responder_request.ts │ │ │ │ ├── responder_requests_table.tsx │ │ │ │ ├── responder_stats.ts │ │ │ │ └── responders.tsx │ │ │ ├── workspace_context.ts │ │ │ └── workspace_page.tsx │ └── tools │ │ ├── downloader.ts │ │ ├── monaco │ │ ├── editor.worker.ts │ │ └── ts.worker.ts │ │ ├── ory.ts │ │ ├── url.ts │ │ └── webauthn.ts │ └── tsconfig.json ├── dev ├── api │ ├── http-client.env.json │ ├── misc │ │ └── send_message.http │ ├── scheduler │ │ └── parse_schedule.http │ ├── search │ │ └── search.http │ ├── security │ │ ├── kratos.http │ │ ├── users_remove.http │ │ └── users_signup.http │ ├── ui │ │ └── state_get.http │ ├── user │ │ ├── get.http │ │ ├── get_by_email.http │ │ ├── get_data.http │ │ ├── get_self.http │ │ └── set_data.http │ └── utils │ │ ├── certificates_private_keys.http │ │ ├── certificates_templates.http │ │ ├── web_scraping_content.http │ │ ├── web_scraping_resources.http │ │ ├── web_security_csp.http │ │ └── webhooks.http └── docker │ ├── kratos.toml │ ├── postgres-and-kratos.yml │ ├── postgres_init.sql │ └── user_identity.schema.json ├── migrations ├── 20240328205631_v1.0.0-beta.1.sql ├── 20240625210528_v1.0.0-beta.2.sql ├── 20240816210706_v1.0.0-beta.2.sql └── sqlite-legacy │ ├── 20230511153224_v1.0.0-alpha.1.sql │ ├── 20230614183626_v1.0.0-alpha.2.sql │ ├── 20230819151535_v1.0.0-alpha.3.sql │ ├── 20231129185220_v1.0.0-alpha.4.sql │ └── 20240126235917_v1.0.0-beta.1.sql ├── package-lock.json ├── package.json ├── rustfmt.toml └── src ├── api.rs ├── config.rs ├── config ├── components_config.rs ├── database_config.rs ├── raw_config.rs ├── scheduler_jobs_config.rs ├── security_config.rs ├── smtp_catch_all_config.rs ├── smtp_config.rs ├── subscriptions_config.rs ├── subscriptions_config │ ├── subscription_certificates_config.rs │ ├── subscription_config.rs │ ├── subscription_web_scraping_config.rs │ ├── subscription_web_security_config.rs │ └── subscription_webhooks_config.rs └── utils_config.rs ├── database.rs ├── directories.rs ├── error.rs ├── error └── error_kind.rs ├── js_runtime.rs ├── js_runtime ├── js_runtime_config.rs └── script_termination_reason.rs ├── logging.rs ├── logging ├── job_log_context.rs ├── metrics_context.rs ├── user_log_context.rs └── utils_resource_log_context.rs ├── main.rs ├── network.rs ├── network ├── dns_resolver.rs ├── email_transport.rs └── ip_addr_ext.rs ├── notifications.rs ├── notifications ├── api_ext.rs ├── database_ext.rs ├── database_ext │ └── raw_notification.rs ├── email.rs ├── email │ ├── email_notification_attachment.rs │ ├── email_notification_attachment_disposition.rs │ └── email_notification_content.rs ├── notification.rs ├── notification_content.rs ├── notification_content_template.rs ├── notification_content_template │ ├── account_activation.rs │ ├── account_recovery.rs │ ├── web_page_content_tracker_changes.rs │ └── web_page_resources_tracker_changes.rs ├── notification_destination.rs └── notification_id.rs ├── scheduler.rs ├── scheduler ├── api_ext.rs ├── cron_ext.rs ├── database_ext.rs ├── database_ext │ └── raw_scheduler_job_stored_data.rs ├── job_ext.rs ├── scheduler_job.rs ├── scheduler_job_config.rs ├── scheduler_job_metadata.rs ├── scheduler_job_retry_state.rs ├── scheduler_job_retry_strategy.rs ├── scheduler_jobs.rs └── scheduler_jobs │ ├── notifications_send_job.rs │ ├── web_page_trackers_fetch_job.rs │ ├── web_page_trackers_schedule_job.rs │ └── web_page_trackers_trigger_job.rs ├── search.rs ├── search ├── api_ext.rs ├── search_filter.rs ├── search_index.rs ├── search_index_initializer.rs ├── search_index_schema_fields.rs └── search_item.rs ├── security.rs ├── security ├── api_ext.rs ├── credentials.rs ├── jwt.rs ├── jwt │ └── claims.rs ├── kratos.rs ├── kratos │ ├── email_template_type.rs │ ├── identity.rs │ ├── identity_traits.rs │ ├── identity_verifiable_address.rs │ └── session.rs └── operator.rs ├── server.rs ├── server ├── app_state.rs ├── extractors.rs ├── extractors │ ├── credentials.rs │ ├── operator.rs │ ├── user.rs │ └── user_share.rs ├── handlers.rs ├── handlers │ ├── scheduler_parse_schedule.rs │ ├── search.rs │ ├── security_subscription_update.rs │ ├── security_users_email.rs │ ├── security_users_get.rs │ ├── security_users_get_by_email.rs │ ├── security_users_get_self.rs │ ├── security_users_remove.rs │ ├── security_users_signup.rs │ ├── send_message.rs │ ├── status_get.rs │ ├── status_set.rs │ ├── ui_state_get.rs │ ├── user_data_get.rs │ ├── user_data_set.rs │ ├── utils_action.rs │ └── webhooks_responders.rs ├── http_errors.rs ├── ui_state.rs └── ui_state │ ├── status.rs │ ├── status_level.rs │ ├── subscription_state.rs │ └── webhook_url_type.rs ├── templates.rs ├── users.rs ├── users ├── api_ext.rs ├── api_ext │ ├── errors.rs │ ├── errors │ │ └── user_signup_error.rs │ ├── user_data_setters.rs │ └── user_data_setters │ │ └── dictionary_data_user_data_setter.rs ├── database_ext.rs ├── database_ext │ ├── raw_user.rs │ ├── raw_user_data.rs │ └── raw_user_share.rs ├── user.rs ├── user_data.rs ├── user_data_key.rs ├── user_data_namespace.rs ├── user_id.rs ├── user_settings.rs ├── user_share.rs ├── user_share │ ├── shared_resource.rs │ └── user_share_id.rs ├── user_subscription.rs └── user_subscription │ ├── client_subscription_features.rs │ ├── subscription_features.rs │ └── subscription_tier.rs ├── utils.rs └── utils ├── api_ext.rs ├── certificates.rs ├── certificates ├── api_ext.rs ├── api_ext │ ├── private_keys_create_params.rs │ ├── private_keys_export_params.rs │ ├── private_keys_update_params.rs │ ├── templates_create_params.rs │ ├── templates_generate_params.rs │ └── templates_update_params.rs ├── certificate_templates.rs ├── certificate_templates │ ├── certificate_attributes.rs │ └── certificate_template.rs ├── database_ext.rs ├── database_ext │ ├── raw_certificate_attributes.rs │ ├── raw_certificate_template.rs │ ├── raw_private_key.rs │ └── raw_private_key_algorithm.rs ├── export_format.rs ├── private_keys.rs ├── private_keys │ ├── private_key.rs │ ├── private_key_algorithm.rs │ ├── private_key_elliptic_curve.rs │ └── private_key_size.rs ├── x509.rs └── x509 │ ├── extended_key_usage.rs │ ├── key_usage.rs │ ├── signature_algorithm.rs │ └── version.rs ├── database_ext.rs ├── database_ext └── raw_util.rs ├── user_share_ext.rs ├── util.rs ├── utils_action.rs ├── utils_action_params.rs ├── utils_action_result.rs ├── utils_action_validation.rs ├── utils_resource.rs ├── utils_resource_operation.rs ├── web_scraping.rs ├── web_scraping ├── api_ext.rs ├── api_ext │ ├── web_page_content_tracker_get_history_params.rs │ ├── web_page_resources_tracker_get_history_params.rs │ ├── web_page_tracker_create_params.rs │ └── web_page_tracker_update_params.rs ├── database_ext.rs ├── database_ext │ ├── raw_web_page_data_revision.rs │ └── raw_web_page_tracker.rs ├── web_page_trackers.rs └── web_page_trackers │ ├── web_page_content.rs │ ├── web_page_content │ ├── web_page_content_revisions_diff.rs │ ├── web_page_content_tracker_tag.rs │ ├── web_scraper_content_request.rs │ └── web_scraper_content_response.rs │ ├── web_page_data_revision.rs │ ├── web_page_resources.rs │ ├── web_page_resources │ ├── web_page_resource.rs │ ├── web_page_resource_content.rs │ ├── web_page_resource_content_data.rs │ ├── web_page_resource_diff_status.rs │ ├── web_page_resources_data.rs │ ├── web_page_resources_revisions_diff.rs │ ├── web_page_resources_tracker_tag.rs │ ├── web_scraper_resources_request.rs │ └── web_scraper_resources_response.rs │ ├── web_page_tracker.rs │ ├── web_page_tracker_kind.rs │ ├── web_page_tracker_settings.rs │ ├── web_page_tracker_tag.rs │ ├── web_scraper.rs │ └── web_scraper │ └── web_scraper_error_response.rs ├── web_security.rs ├── web_security ├── api_ext.rs ├── api_ext │ ├── content_security_policies_create_params.rs │ ├── content_security_policies_serialize_params.rs │ ├── content_security_policies_update_params.rs │ ├── content_security_policy_content.rs │ └── csp_meta_parser.rs ├── csp.rs ├── csp │ ├── content_security_policies.rs │ ├── content_security_policies │ │ ├── content_security_policy.rs │ │ ├── content_security_policy_directive.rs │ │ ├── content_security_policy_require_trusted_types_for_directive_value.rs │ │ ├── content_security_policy_sandbox_directive_value.rs │ │ ├── content_security_policy_trusted_types_directive_value.rs │ │ └── content_security_policy_webrtc_directive_value.rs │ └── content_security_policy_source.rs ├── database_ext.rs └── database_ext │ └── raw_content_security_policy.rs ├── webhooks.rs └── webhooks ├── api_ext.rs ├── api_ext ├── responders_create_params.rs ├── responders_request_create_params.rs └── responders_update_params.rs ├── database_ext.rs ├── database_ext ├── raw_responder.rs └── raw_responder_request.rs ├── responders.rs └── responders ├── responder.rs ├── responder_location.rs ├── responder_method.rs ├── responder_path_type.rs ├── responder_request.rs ├── responder_script_context.rs ├── responder_script_result.rs ├── responder_settings.rs └── responder_stats.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | rustflags = ["--cfg", "uuid_unstable"] 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/.dockerignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | target/ 3 | dist 4 | .parcel-cache 5 | .docusaurus 6 | 7 | .idea/ 8 | .env 9 | .DS_Store 10 | 11 | *.private.env.json 12 | secutils.toml 13 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit ${1} 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | if ! cargo sqlx prepare --check 6 | then 7 | echo "Database schema snapshot should be updated." 8 | echo "Run `cargo sqlx prepare` first." 9 | exit 1 10 | fi 11 | 12 | if ! cargo +nightly fmt --all -- --check 13 | then 14 | echo "There are some code style issues." 15 | echo "Run `cargo fmt` first." 16 | exit 1 17 | fi 18 | 19 | if ! cargo clippy --all --all-targets -- -D warnings 20 | then 21 | echo "There are some Clippy issues." 22 | exit 1 23 | fi 24 | 25 | if ! cargo test 26 | then 27 | echo "There are some test issues." 28 | exit 1 29 | fi 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.proxyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://127.0.0.1:7070/" 4 | }, 5 | "/docs": { 6 | "target": "http://127.0.0.1:7373/" 7 | }, 8 | "/self-service": { 9 | "target": "http://127.0.0.1:4433/" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.sqlx/query-0121880afb20a899856cb784e752561924a99b073e6fba4e669257b3a4c15ff1.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO notifications (destination, content, scheduled_at) VALUES ($1, $2, $3) RETURNING id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Bytea", 15 | "Bytea", 16 | "Timestamptz" 17 | ] 18 | }, 19 | "nullable": [ 20 | false 21 | ] 22 | }, 23 | "hash": "0121880afb20a899856cb784e752561924a99b073e6fba4e669257b3a4c15ff1" 24 | } 25 | -------------------------------------------------------------------------------- /.sqlx/query-0198d93680a7781f92e2740c73703404ac24cb09cf70c18ff3297a6524cb861d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_web_scraping_trackers\n WHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "0198d93680a7781f92e2740c73703404ac24cb09cf70c18ff3297a6524cb861d" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-06a96093a952ca7b5d28c78a2ffc59731fd279308f8acfe505b21f867048aefe.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n UPDATE user_data_web_security_csp\n SET name = $3, directives = $4, updated_at = $5\n WHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Timestamptz" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "06a96093a952ca7b5d28c78a2ffc59731fd279308f8acfe505b21f867048aefe" 18 | } 19 | -------------------------------------------------------------------------------- /.sqlx/query-06b024f294f9626f281b064c118b4a192a510610f08d997d3f2a756491446158.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n UPDATE user_data_web_scraping_trackers\n SET job_id = $2\n WHERE id = $1\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "06b024f294f9626f281b064c118b4a192a510610f08d997d3f2a756491446158" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-08324f9b9c086bde12d5a6ce2c3d1b7c8d6b02e2a6b5bbc3c0776dadf9e2efde.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nUPDATE user_data_web_scraping_trackers\nSET name = $4, url = $5, job_config = $6, data = $7, job_id = $8, updated_at = $9\nWHERE user_id = $1 AND id = $2 AND kind = $3\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Bytea", 11 | "Text", 12 | "Text", 13 | "Bytea", 14 | "Bytea", 15 | "Uuid", 16 | "Timestamptz" 17 | ] 18 | }, 19 | "nullable": [] 20 | }, 21 | "hash": "08324f9b9c086bde12d5a6ce2c3d1b7c8d6b02e2a6b5bbc3c0776dadf9e2efde" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-0dc7a87ffa2ec4c15a5bc653b9719a304127581d930d47d53b7cd8f333f5cf35.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_web_scraping_trackers_history\n WHERE user_id = $1 AND tracker_id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "0dc7a87ffa2ec4c15a5bc653b9719a304127581d930d47d53b7cd8f333f5cf35" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-2184c690e1b380b7f8b8aa237c495b09b23b7286e5753183015be3cdb021826e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, handle, name, keywords, parent_id\nFROM utils\nORDER BY parent_id NULLS FIRST, id\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int4" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "handle", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "name", 19 | "type_info": "Text" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "keywords", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "parent_id", 29 | "type_info": "Int4" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [] 34 | }, 35 | "nullable": [ 36 | false, 37 | false, 38 | false, 39 | true, 40 | true 41 | ] 42 | }, 43 | "hash": "2184c690e1b380b7f8b8aa237c495b09b23b7286e5753183015be3cdb021826e" 44 | } 45 | -------------------------------------------------------------------------------- /.sqlx/query-236117c8d13b7643a38ecc1a87fedb730d16aad41adf8bc1a8e30fbd4b46ea92.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO user_data_web_scraping_trackers (user_id, id, name, url, kind, job_id, job_config, data, created_at, updated_at)\n VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Text", 12 | "Bytea", 13 | "Uuid", 14 | "Bytea", 15 | "Bytea", 16 | "Timestamptz", 17 | "Timestamptz" 18 | ] 19 | }, 20 | "nullable": [] 21 | }, 22 | "hash": "236117c8d13b7643a38ecc1a87fedb730d16aad41adf8bc1a8e30fbd4b46ea92" 23 | } 24 | -------------------------------------------------------------------------------- /.sqlx/query-25dd81897ddf37b727a99f4048aefda36257f0697b8de4c08b182c3486f1c8e5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nDELETE FROM user_shares\nWHERE id = $1\nRETURNING id as \"id!\", user_id as \"user_id!\", resource as \"resource!\", created_at as \"created_at!\"\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id!", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "user_id!", 14 | "type_info": "Uuid" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "resource!", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at!", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Uuid" 30 | ] 31 | }, 32 | "nullable": [ 33 | false, 34 | false, 35 | false, 36 | false 37 | ] 38 | }, 39 | "hash": "25dd81897ddf37b727a99f4048aefda36257f0697b8de4c08b182c3486f1c8e5" 40 | } 41 | -------------------------------------------------------------------------------- /.sqlx/query-27ab8c3e799763d51be0bedf5163bf496a6e24615fd8fb12a5a841645034839b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nUPDATE user_data_certificates_certificate_templates\nSET name = $3, attributes = $4, updated_at = $5\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Timestamptz" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "27ab8c3e799763d51be0bedf5163bf496a6e24615fd8fb12a5a841645034839b" 18 | } 19 | -------------------------------------------------------------------------------- /.sqlx/query-2808bb0b0fcbe605fd8c4033b5b66bfb99905906782e93f462724553646c3b45.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, name, directives, created_at, updated_at\nFROM user_data_web_security_csp\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "name", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "directives", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "updated_at", 29 | "type_info": "Timestamptz" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Uuid", 35 | "Uuid" 36 | ] 37 | }, 38 | "nullable": [ 39 | false, 40 | false, 41 | false, 42 | false, 43 | false 44 | ] 45 | }, 46 | "hash": "2808bb0b0fcbe605fd8c4033b5b66bfb99905906782e93f462724553646c3b45" 47 | } 48 | -------------------------------------------------------------------------------- /.sqlx/query-2c0b85fd43a4c41034dc025ea26e6f63337cd20e9fdc2f99457de7f7214d5f0c.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nDELETE FROM user_data_certificates_certificate_templates\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "2c0b85fd43a4c41034dc025ea26e6f63337cd20e9fdc2f99457de7f7214d5f0c" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-3224d177c41738db1c75252a7183dc9f5bfd376dc90b696ac40e99798225417b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_web_security_csp\n WHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "3224d177c41738db1c75252a7183dc9f5bfd376dc90b696ac40e99798225417b" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-332b530d376ec5f092d2a12ec371bf9f49547575bae1e7f11fc6ce5d39308523.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT id, responder_id, data, created_at\n FROM user_data_webhooks_responders_history\n WHERE user_id = $1 AND responder_id = $2\n ORDER BY created_at\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "responder_id", 14 | "type_info": "Uuid" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "data", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Uuid", 30 | "Uuid" 31 | ] 32 | }, 33 | "nullable": [ 34 | false, 35 | false, 36 | false, 37 | false 38 | ] 39 | }, 40 | "hash": "332b530d376ec5f092d2a12ec371bf9f49547575bae1e7f11fc6ce5d39308523" 41 | } 42 | -------------------------------------------------------------------------------- /.sqlx/query-3bb01e88f57c80a3537d7bf39fd9962592802ea58eed0136c8b655610bc7f919.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users (id, email, handle, created_at)\nVALUES ( $1, $2, $3, $4 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Text", 10 | "Text", 11 | "Timestamptz" 12 | ] 13 | }, 14 | "nullable": [] 15 | }, 16 | "hash": "3bb01e88f57c80a3537d7bf39fd9962592802ea58eed0136c8b655610bc7f919" 17 | } 18 | -------------------------------------------------------------------------------- /.sqlx/query-47b3211344c71c951ab4bb8e71bacd47fc54001e7d3a60796039454404011fa1.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT * FROM notifications WHERE id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int4" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "destination", 14 | "type_info": "Bytea" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "content", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "scheduled_at", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Int4" 30 | ] 31 | }, 32 | "nullable": [ 33 | false, 34 | false, 35 | false, 36 | false 37 | ] 38 | }, 39 | "hash": "47b3211344c71c951ab4bb8e71bacd47fc54001e7d3a60796039454404011fa1" 40 | } 41 | -------------------------------------------------------------------------------- /.sqlx/query-4f33d79bf3c4684dd292b491f1ec550261df7ebbd2bf2d6f904a1f9749fd5a80.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_webhooks_responders\n WHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "4f33d79bf3c4684dd292b491f1ec550261df7ebbd2bf2d6f904a1f9749fd5a80" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-61a42354728ee9fffa05bb0b70b1aa5b056d9150c63f7f41aa51d36b2c9734fa.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_data_certificates_private_keys (user_id, id, name, alg, pkcs8, encrypted, created_at, updated_at)\nVALUES ( $1, $2, $3, $4, $5, $6, $7, $8 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Bytea", 13 | "Bool", 14 | "Timestamptz", 15 | "Timestamptz" 16 | ] 17 | }, 18 | "nullable": [] 19 | }, 20 | "hash": "61a42354728ee9fffa05bb0b70b1aa5b056d9150c63f7f41aa51d36b2c9734fa" 21 | } 22 | -------------------------------------------------------------------------------- /.sqlx/query-888b251abf2d1f82c2bec9dc07bb172797fb301bca932635f1ed398c73e8bd92.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO user_data_web_security_csp (user_id, id, name, directives, created_at, updated_at)\n VALUES ( $1, $2, $3, $4, $5, $6 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Timestamptz", 13 | "Timestamptz" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "888b251abf2d1f82c2bec9dc07bb172797fb301bca932635f1ed398c73e8bd92" 19 | } 20 | -------------------------------------------------------------------------------- /.sqlx/query-9347d958c7d809bf925cc591b552167823cb45ee16412895ce0148080c210c00.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO user_data_webhooks_responders_history (user_id, id, responder_id, data, created_at)\n VALUES ( $1, $2, $3, $4, $5 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Uuid", 11 | "Bytea", 12 | "Timestamptz" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "9347d958c7d809bf925cc591b552167823cb45ee16412895ce0148080c210c00" 18 | } 19 | -------------------------------------------------------------------------------- /.sqlx/query-9349bd205fb7e6a1f84958ade34112ee576e1854545289766871e46180f99570.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_webhooks_responders_history\n WHERE user_id = $1 AND responder_id = $2 AND id = $3\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Uuid" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "9349bd205fb7e6a1f84958ade34112ee576e1854545289766871e46180f99570" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-98523a4ee01682cdc05ddeb9a624b4a3c14ff1b80e59f6ae9e759af2de97fd6c.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_webhooks_responders_history\n WHERE user_id = $1 AND responder_id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "98523a4ee01682cdc05ddeb9a624b4a3c14ff1b80e59f6ae9e759af2de97fd6c" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a231b8dc4515d04e7e0021ecc27cf83e6263521afc7ae5a06faf7b5a5c876ce7.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, user_id, resource, created_at\nFROM user_shares\nWHERE user_id = $1 AND resource = $2\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "user_id", 14 | "type_info": "Uuid" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "resource", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Uuid", 30 | "Bytea" 31 | ] 32 | }, 33 | "nullable": [ 34 | false, 35 | false, 36 | false, 37 | false 38 | ] 39 | }, 40 | "hash": "a231b8dc4515d04e7e0021ecc27cf83e6263521afc7ae5a06faf7b5a5c876ce7" 41 | } 42 | -------------------------------------------------------------------------------- /.sqlx/query-a7fedd16f798de891e278fb9ee10064368010a6fc5f4399b0e60bb860f8d1222.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_data_certificates_certificate_templates (user_id, id, name, attributes, created_at, updated_at)\nVALUES ( $1, $2, $3, $4, $5, $6 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Timestamptz", 13 | "Timestamptz" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "a7fedd16f798de891e278fb9ee10064368010a6fc5f4399b0e60bb860f8d1222" 19 | } 20 | -------------------------------------------------------------------------------- /.sqlx/query-a807490d7d3eaad38f8ac404bdef695fc6d7d8e5babec9fec19e742fd3cb5aff.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nDELETE FROM user_data_certificates_private_keys\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "a807490d7d3eaad38f8ac404bdef695fc6d7d8e5babec9fec19e742fd3cb5aff" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-a88d81a3637a342d885e39262da39ee50ce9695b3f520f4d61e063d8559c686e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO user_data_web_scraping_trackers_history (user_id, id, tracker_id, data, created_at)\n VALUES ( $1, $2, $3, $4, $5 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Uuid", 11 | "Bytea", 12 | "Timestamptz" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "a88d81a3637a342d885e39262da39ee50ce9695b3f520f4d61e063d8559c686e" 18 | } 19 | -------------------------------------------------------------------------------- /.sqlx/query-a9f9ecff772fe17c0a99cfa86c01572cdfd8d6c4058cab7e3d9af84b17e5d3c5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_subscriptions (user_id, tier, started_at, ends_at, trial_started_at, trial_ends_at)\nVALUES ( $1, $2, $3, $4, $5, $6 )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Int4", 10 | "Timestamptz", 11 | "Timestamptz", 12 | "Timestamptz", 13 | "Timestamptz" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "a9f9ecff772fe17c0a99cfa86c01572cdfd8d6c4058cab7e3d9af84b17e5d3c5" 19 | } 20 | -------------------------------------------------------------------------------- /.sqlx/query-ae246ca497b982b978aa9be511679f2a8791a662b0eb85085592c791f18b881f.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO users (id, email, handle, created_at)\nVALUES ( $1, $2, $3, $4 )\nON CONFLICT(id) DO UPDATE SET email=excluded.email, handle=excluded.handle, created_at=excluded.created_at\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Text", 10 | "Text", 11 | "Timestamptz" 12 | ] 13 | }, 14 | "nullable": [] 15 | }, 16 | "hash": "ae246ca497b982b978aa9be511679f2a8791a662b0eb85085592c791f18b881f" 17 | } 18 | -------------------------------------------------------------------------------- /.sqlx/query-aef3a29ec6dfaa83bad8edb946c7ef9f25b5a13e10567f287a5d7275d1ac8e1e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n DELETE FROM user_data_web_scraping_trackers_history\n WHERE user_id = $1 AND tracker_id = $2 AND id = $3\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Uuid" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "aef3a29ec6dfaa83bad8edb946c7ef9f25b5a13e10567f287a5d7275d1ac8e1e" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-b8c1d7fb1b4e0572abe4810e77361ee603c2937d643a47e268da19fa56ea0be9.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, name, attributes, created_at, updated_at\nFROM user_data_certificates_certificate_templates\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "name", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "attributes", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "updated_at", 29 | "type_info": "Timestamptz" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Uuid", 35 | "Uuid" 36 | ] 37 | }, 38 | "nullable": [ 39 | false, 40 | false, 41 | false, 42 | false, 43 | false 44 | ] 45 | }, 46 | "hash": "b8c1d7fb1b4e0572abe4810e77361ee603c2937d643a47e268da19fa56ea0be9" 47 | } 48 | -------------------------------------------------------------------------------- /.sqlx/query-be55e9297145f15ad560a3193a9f80184b8cd3824e8feab61ad2017528cecf7b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT id, name, directives, created_at, updated_at\n FROM user_data_web_security_csp\n WHERE user_id = $1\n ORDER BY updated_at\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "name", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "directives", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "updated_at", 29 | "type_info": "Timestamptz" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Uuid" 35 | ] 36 | }, 37 | "nullable": [ 38 | false, 39 | false, 40 | false, 41 | false, 42 | false 43 | ] 44 | }, 45 | "hash": "be55e9297145f15ad560a3193a9f80184b8cd3824e8feab61ad2017528cecf7b" 46 | } 47 | -------------------------------------------------------------------------------- /.sqlx/query-bf3ca443d10c22de3e0db2abe082669b2fdf999b34748f456f8045e8124fa816.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nUPDATE scheduler_jobs\nSET stopped = $2, extra = $3\nWHERE id = $1\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Bool", 10 | "Bytea" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "bf3ca443d10c22de3e0db2abe082669b2fdf999b34748f456f8045e8124fa816" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-c0e494494848b3db7599a64d0012b403a8081df37b2f285fa1c5a417c543f8c5.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT id FROM notifications WHERE scheduled_at <= $1 AND id > $2 ORDER BY scheduled_at, id LIMIT $3;", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int4" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Timestamptz", 15 | "Int4", 16 | "Int8" 17 | ] 18 | }, 19 | "nullable": [ 20 | false 21 | ] 22 | }, 23 | "hash": "c0e494494848b3db7599a64d0012b403a8081df37b2f285fa1c5a417c543f8c5" 24 | } 25 | -------------------------------------------------------------------------------- /.sqlx/query-c91a9e3f7932e6ce435499aade8500aa507f4007f275a80d907ac20ae6e13526.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_data (user_id, namespace, key, value, timestamp)\nVALUES ( $1, $2, $3, $4, $5 )\nON CONFLICT(user_id, namespace, key) DO UPDATE SET value=excluded.value, timestamp=excluded.timestamp\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Text", 10 | "Text", 11 | "Bytea", 12 | "Timestamptz" 13 | ] 14 | }, 15 | "nullable": [] 16 | }, 17 | "hash": "c91a9e3f7932e6ce435499aade8500aa507f4007f275a80d907ac20ae6e13526" 18 | } 19 | -------------------------------------------------------------------------------- /.sqlx/query-cd3bebc681cf49b4fd801f5132be7930053baecafe364fdd33c70ad55690b63f.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_subscriptions (user_id, tier, started_at, ends_at, trial_started_at, trial_ends_at)\nVALUES ( $1, $2, $3, $4, $5, $6 )\nON CONFLICT(user_id) DO UPDATE SET tier=excluded.tier, started_at=excluded.started_at, ends_at=excluded.ends_at, trial_started_at=excluded.trial_started_at, trial_ends_at=excluded.trial_ends_at\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Int4", 10 | "Timestamptz", 11 | "Timestamptz", 12 | "Timestamptz", 13 | "Timestamptz" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "cd3bebc681cf49b4fd801f5132be7930053baecafe364fdd33c70ad55690b63f" 19 | } 20 | -------------------------------------------------------------------------------- /.sqlx/query-cef012896306769cfa83066194ef4e6f992c4e57c12457cb0b4e8e75c37fef0e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, name, attributes, created_at, updated_at\nFROM user_data_certificates_certificate_templates\nWHERE user_id = $1\nORDER BY updated_at\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "name", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "attributes", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "updated_at", 29 | "type_info": "Timestamptz" 30 | } 31 | ], 32 | "parameters": { 33 | "Left": [ 34 | "Uuid" 35 | ] 36 | }, 37 | "nullable": [ 38 | false, 39 | false, 40 | false, 41 | false, 42 | false 43 | ] 44 | }, 45 | "hash": "cef012896306769cfa83066194ef4e6f992c4e57c12457cb0b4e8e75c37fef0e" 46 | } 47 | -------------------------------------------------------------------------------- /.sqlx/query-d3dff488871283501e349cd285509197be3ab78d76fea4127246bee48a454dbe.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT r.id AS responder_id, COUNT(rh.id) AS request_count, MAX(rh.created_at) AS last_requested_at\nFROM user_data_webhooks_responders AS r\nJOIN user_data_webhooks_responders_history AS rh\nON r.id = rh.responder_id\nWHERE r.user_id = $1\nGROUP BY r.id\nORDER BY r.updated_at\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "responder_id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "request_count", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "last_requested_at", 19 | "type_info": "Timestamptz" 20 | } 21 | ], 22 | "parameters": { 23 | "Left": [ 24 | "Uuid" 25 | ] 26 | }, 27 | "nullable": [ 28 | false, 29 | null, 30 | null 31 | ] 32 | }, 33 | "hash": "d3dff488871283501e349cd285509197be3ab78d76fea4127246bee48a454dbe" 34 | } 35 | -------------------------------------------------------------------------------- /.sqlx/query-d76c9d4c1ec5d3d058cfc60977a8a91878a878d716da8e6052a464689705d6c4.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nDELETE FROM user_data\nWHERE user_id = $1 AND namespace = $2 AND key = $3\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Text", 10 | "Text" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "d76c9d4c1ec5d3d058cfc60977a8a91878a878d716da8e6052a464689705d6c4" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-db62941637a00720920aefdbf9b2a84ec1b3191a7c4b73df4144e9f0067d43f4.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nUPDATE user_data_certificates_private_keys\nSET name = $3, pkcs8 = $4, encrypted = $5, updated_at = $6\nWHERE user_id = $1 AND id = $2\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Bytea", 12 | "Bool", 13 | "Timestamptz" 14 | ] 15 | }, 16 | "nullable": [] 17 | }, 18 | "hash": "db62941637a00720920aefdbf9b2a84ec1b3191a7c4b73df4144e9f0067d43f4" 19 | } 20 | -------------------------------------------------------------------------------- /.sqlx/query-dc139034ac870e4570fabdd382f5d6966ad5f1590f5e714e2bade89dda8a67f8.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT id, user_id, resource, created_at\nFROM user_shares\nWHERE id = $1\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "user_id", 14 | "type_info": "Uuid" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "resource", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "created_at", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Uuid" 30 | ] 31 | }, 32 | "nullable": [ 33 | false, 34 | false, 35 | false, 36 | false 37 | ] 38 | }, 39 | "hash": "dc139034ac870e4570fabdd382f5d6966ad5f1590f5e714e2bade89dda8a67f8" 40 | } 41 | -------------------------------------------------------------------------------- /.sqlx/query-dcb605cadaeb5e99f0ce5351e64c0da818f3b818ab2693f1ea6f3d1f134a253c.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nSELECT user_id, key, value, timestamp\nFROM user_data\nWHERE user_id = $1 AND namespace = $2 AND key = $3\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "user_id", 9 | "type_info": "Uuid" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "key", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "value", 19 | "type_info": "Bytea" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "timestamp", 24 | "type_info": "Timestamptz" 25 | } 26 | ], 27 | "parameters": { 28 | "Left": [ 29 | "Uuid", 30 | "Text", 31 | "Text" 32 | ] 33 | }, 34 | "nullable": [ 35 | false, 36 | false, 37 | false, 38 | false 39 | ] 40 | }, 41 | "hash": "dcb605cadaeb5e99f0ce5351e64c0da818f3b818ab2693f1ea6f3d1f134a253c" 42 | } 43 | -------------------------------------------------------------------------------- /.sqlx/query-ddbae2f82f3d5bb5f89536340277b6c90c00ddf699c52657bb56ea84ad3d5166.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT extra FROM scheduler_jobs WHERE id = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "extra", 9 | "type_info": "Bytea" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Uuid" 15 | ] 16 | }, 17 | "nullable": [ 18 | true 19 | ] 20 | }, 21 | "hash": "ddbae2f82f3d5bb5f89536340277b6c90c00ddf699c52657bb56ea84ad3d5166" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM notifications WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-e2b703445a00437ce922f2e3c134330ee11d6cc70b46338eec5cd8d11e509c6e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "UPDATE scheduler_jobs SET extra = $2 WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Bytea" 10 | ] 11 | }, 12 | "nullable": [] 13 | }, 14 | "hash": "e2b703445a00437ce922f2e3c134330ee11d6cc70b46338eec5cd8d11e509c6e" 15 | } 16 | -------------------------------------------------------------------------------- /.sqlx/query-ebf1133ee28bcc9f96055d4011c488493a1c384ea5d6ed22d732cecedf20242e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n UPDATE user_data_webhooks_responders\n SET name = $3, location = $4, method = $5, enabled = $6, settings = $7, updated_at = $8\n WHERE user_id = $1 AND id = $2 AND NOT EXISTS(\n SELECT id FROM user_data_webhooks_responders \n WHERE user_id = $1 AND id != $2 AND location = $4 AND (method = $9 OR method = $5 OR $5 = $9)\n )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Text", 12 | "Bytea", 13 | "Bool", 14 | "Bytea", 15 | "Timestamptz", 16 | "Bytea" 17 | ] 18 | }, 19 | "nullable": [] 20 | }, 21 | "hash": "ebf1133ee28bcc9f96055d4011c488493a1c384ea5d6ed22d732cecedf20242e" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-eefc34b063aaae0f6b7c2ae9a08879234871dae520a60c3007dcc2eea2322aa8.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nINSERT INTO user_shares (id, user_id, resource, created_at)\nVALUES ($1, $2, $3, $4)\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Bytea", 11 | "Timestamptz" 12 | ] 13 | }, 14 | "nullable": [] 15 | }, 16 | "hash": "eefc34b063aaae0f6b7c2ae9a08879234871dae520a60c3007dcc2eea2322aa8" 17 | } 18 | -------------------------------------------------------------------------------- /.sqlx/query-fa59a4e24dfaeaeb848f9e4f7759f63caa970476e4b4379cf71f0d5f133b3fa3.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nDELETE FROM users\nWHERE email = $1\nRETURNING id\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Uuid" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Text" 15 | ] 16 | }, 17 | "nullable": [ 18 | false 19 | ] 20 | }, 21 | "hash": "fa59a4e24dfaeaeb848f9e4f7759f63caa970476e4b4379cf71f0d5f133b3fa3" 22 | } 23 | -------------------------------------------------------------------------------- /.sqlx/query-fbdeb3d83769b69a82a5207f271a46a6a3a78158d2e0a253f5026b1ac82bb83d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n WITH new_responder(user_id, id, name, location, method, enabled, settings, created_at, updated_at) AS (\n VALUES ( $1::uuid, $2::uuid, $3, $4, $5::bytea, $6::bool, $7::bytea, $8::timestamptz, $9::timestamptz )\n )\n INSERT INTO user_data_webhooks_responders (user_id, id, name, location, method, enabled, settings, created_at, updated_at)\n SELECT * FROM new_responder\n WHERE NOT EXISTS(\n SELECT id FROM user_data_webhooks_responders \n WHERE user_id = $1 AND location = $4 AND (method = $10 OR $5 = $10)\n )\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Uuid", 9 | "Uuid", 10 | "Text", 11 | "Text", 12 | "Bytea", 13 | "Bool", 14 | "Bytea", 15 | "Timestamptz", 16 | "Timestamptz", 17 | "Bytea" 18 | ] 19 | }, 20 | "nullable": [] 21 | }, 22 | "hash": "fbdeb3d83769b69a82a5207f271a46a6a3a78158d2e0a253f5026b1ac82bb83d" 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | # Paths that should be ignored by all Docker images 2 | node_modules 3 | .idea 4 | .DS_Store 5 | dev 6 | .parcel-cache 7 | target 8 | dist 9 | .env 10 | .nvmrc 11 | .github 12 | .husky 13 | Dockerfile 14 | Dockerfile.webui 15 | *.md 16 | LICENSE 17 | secutils.toml 18 | rustfmt.toml 19 | 20 | # Path that should be ignored by api Docker image 21 | /components/secutils-docs 22 | /components/secutils-webui 23 | *.json 24 | -------------------------------------------------------------------------------- /Dockerfile.docs: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM node:22-alpine3.21 AS builder 4 | ARG SECUTILS_ENV="prod" 5 | ENV SECUTILS_ENV=${SECUTILS_ENV} 6 | WORKDIR /app 7 | 8 | # Copy workspace root `package.json` and `package-lock.json` files, 9 | # and `package.json` file from the component, to just install dependencies. 10 | COPY ["./*.json", "./"] 11 | COPY ["./components/secutils-docs/package.json", "./components/secutils-docs/"] 12 | COPY ["./components/secutils-docs/*.js", "./components/secutils-docs/"] 13 | RUN set -x && npm ci --ws 14 | 15 | # Now copy the rest of the component files and build it. 16 | COPY ["./components/secutils-docs/src", "./components/secutils-docs/src"] 17 | COPY ["./components/secutils-docs/static", "./components/secutils-docs/static"] 18 | COPY ["./components/secutils-docs/docs", "./components/secutils-docs/docs"] 19 | COPY ["./components/secutils-docs/blog", "./components/secutils-docs/blog"] 20 | RUN set -x && npm run build --ws 21 | 22 | FROM nginxinc/nginx-unprivileged:alpine3.21-slim 23 | COPY --from=builder ["/app/components/secutils-docs/build/", "/usr/share/nginx/html/docs"] 24 | COPY ["./components/secutils-docs/config/nginx.conf", "/etc/nginx/conf.d/default.conf"] 25 | -------------------------------------------------------------------------------- /Dockerfile.webui: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM --platform=$BUILDPLATFORM node:22-alpine3.21 AS builder 4 | WORKDIR /app 5 | 6 | # See, https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#node-gyp-alpine 7 | RUN apk add --no-cache python3 make g++ 8 | 9 | # Copy workspace root `package.json` and `package-lock.json` files, 10 | # and `package.json` file from the component, to just install dependencies. 11 | COPY ["./*.json", "./"] 12 | COPY ["./components/secutils-webui/package.json", "./components/secutils-webui/"] 13 | RUN set -x && npm ci --ws 14 | 15 | # Now copy the rest of the component files and build it. 16 | COPY ["./components/secutils-webui", "./components/secutils-webui"] 17 | RUN set -x && npm run build --ws 18 | 19 | FROM nginxinc/nginx-unprivileged:alpine3.21-slim 20 | COPY --from=builder ["/app/components/secutils-webui/dist/", "/usr/share/nginx/html/"] 21 | COPY ["./components/secutils-webui/config/nginx.conf", "/etc/nginx/conf.d/default.conf"] 22 | -------------------------------------------------------------------------------- /Dockerfile.webui.dockerignore: -------------------------------------------------------------------------------- 1 | # Paths that should be ignored by all Docker images 2 | node_modules 3 | .idea 4 | .DS_Store 5 | dev 6 | .parcel-cache 7 | target 8 | dist 9 | .env 10 | .nvmrc 11 | .github 12 | .husky 13 | Dockerfile 14 | *.md 15 | LICENSE 16 | secutils.toml 17 | rustfmt.toml 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you've found a security vulnerability in the Secutils.dev codebase, you can responsibly disclose it by sending a summary to security@secutils.dev. 6 | We will review the potential threat and work to fix it as quickly as possible. 7 | 8 | Although we do not currently have a bug bounty program, we are extremely grateful to those who take the time to share their findings with us. 9 | 10 | Thank you! 11 | -------------------------------------------------------------------------------- /assets/logo/secutils-logo-initials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/assets/logo/secutils-logo-initials.png -------------------------------------------------------------------------------- /assets/logo/secutils-logo-source.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 16 | 26 | Secutils 34 | 35 | 36 | -------------------------------------------------------------------------------- /assets/logo/secutils-logo-with-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/assets/logo/secutils-logo-with-text.png -------------------------------------------------------------------------------- /assets/templates/account_activation_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Activate your Secutils.dev account 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

Hi there,

12 |

Thanks for signing up! To activate your account, please click the button below:

13 | Activate my account 14 |

Alternatively, copy and paste the following URL into your browser:

15 |

{{{encoded_activation_link}}}

16 |

Or, simply copy and paste the following code into the account activation form:

17 |

{{{encoded_activation_code}}}

18 |

If you have any trouble activating your account, please email us at contact@secutils.dev 19 | or simply reply to this email.

20 | Secutils.dev logo 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/templates/account_recovery_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Recover access to your Secutils.dev account 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

Hi there,

12 |

You recently requested to recover access to your account. To do so, please copy and paste the following code into the account recovery form:

13 |

{{{encoded_recovery_code}}}

14 |

If you did not request to recover your account, please ignore this email.

15 |

If you have any trouble recovering your account, please email us at contact@secutils.dev 16 | or simply reply to this email.

17 | Secutils.dev logo 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/templates/email_styles.hbs: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /assets/templates/web_page_content_tracker_changes_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "{{tracker_name}}" tracker detected content changes 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

"{{tracker_name}}" tracker detected content changes

12 |

Current content: {{content}}

13 |

To learn more, visit the Content trackers page:

14 | Web Scraping → Content trackers 15 |

If the button above doesn't work, you can navigate to the following URL directly:

16 |

{{back_link}}

17 | Secutils.dev logo 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/templates/web_page_content_tracker_changes_error_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "{{tracker_name}}" tracker failed to check for content changes 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

"{{tracker_name}}" tracker failed to check for content changes

12 |

There was an error while checking content: {{error_message}}.

13 |

To check the tracker configuration and re-try, visit the Content trackers page:

14 | Web Scraping → Content trackers 15 |

If the button above doesn't work, you can navigate to the following URL directly:

16 |

{{back_link}}

17 | Secutils.dev logo 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/templates/web_page_resources_tracker_changes_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "{{tracker_name}}" tracker detected {{changes_count}} changes in resources 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

"{{tracker_name}}" tracker detected {{changes_count}} changes in resources

12 |

To learn more, visit the Resources trackers page:

13 | Web Scraping → Resources trackers 14 |

If the button above doesn't work, you can navigate to the following URL directly:

15 |

{{back_link}}

16 | Secutils.dev logo 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/templates/web_page_resources_tracker_changes_error_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "{{tracker_name}}" tracker failed to check for changes in resources 5 | 6 | 7 | {{> email_styles}} 8 | 9 | 10 |
11 |

"{{tracker_name}}" tracker failed to check for changes in resources

12 |

There was an error while checking resources: {{error_message}}.

13 |

To check the tracker configuration and re-try, visit the Resources trackers page:

14 | Web Scraping → Resources trackers 15 |

If the button above doesn't work, you can navigate to the following URL directly:

16 |

{{back_link}}

17 | Secutils.dev logo 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // generated by `sqlx migrate build-script` 2 | fn main() { 3 | // trigger recompilation when a new migration is added 4 | println!("cargo:rerun-if-changed=migrations"); 5 | } 6 | -------------------------------------------------------------------------------- /components/secutils-docs/README.md: -------------------------------------------------------------------------------- 1 | The documentation website for Secutils.dev. 2 | 3 | ## Getting started 4 | 5 | Install all the required dependencies with `npm install` and run the UI in watch mode with `npm run start`. 6 | 7 | ### Usage 8 | 9 | The docs website should be accessible at http://localhost:7373. 10 | 11 | -------------------------------------------------------------------------------- /components/secutils-docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /components/secutils-docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | azasypkin: 2 | name: Aleh Zasypkin 3 | title: Creator of Secutils.dev 4 | url: https://github.com/azasypkin 5 | image_url: https://avatars.githubusercontent.com/u/1713708?v=4 6 | email: dev@secutils.dev 7 | -------------------------------------------------------------------------------- /components/secutils-docs/config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | listen [::]:8080; 4 | server_name localhost; 5 | 6 | gzip on; 7 | gzip_types text/plain text/html text/css application/javascript text/xml application/xml image/svg+xml; 8 | 9 | #access_log /var/log/nginx/host.access.log main; 10 | 11 | location / { 12 | root /usr/share/nginx/html; 13 | try_files $uri $uri/index.html $uri/ $uri.html =404; 14 | error_page 404 /docs/404.html; 15 | } 16 | 17 | add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; img-src 'self' data: secutils.dev github.com avatars.githubusercontent.com"; 18 | } 19 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/guides/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Guides", 3 | "collapsed": false, 4 | "collapsible": false, 5 | "position": 2, 6 | "link": { 7 | "type": "generated-index", 8 | "description": "Learn the most important Secutils.dev functionality with step-by-step guides and detailed video instructions." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/guides/digital_certificates/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Digital Certificates", 3 | "collapsed": false, 4 | "collapsible": false, 5 | "position": 2, 6 | "link": { 7 | "type": "generated-index", 8 | "description": "Learn more about Secutils.dev tools for dealing with digital certificates and private keys." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/guides/web_scraping/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Web Scraping", 3 | "collapsed": false, 4 | "collapsible": false, 5 | "position": 5, 6 | "link": { 7 | "type": "generated-index", 8 | "description": "Learn more about Secutils.dev tools for dealing with web scraping." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/guides/web_security/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Web Security", 3 | "collapsed": false, 4 | "collapsible": false, 5 | "position": 4, 6 | "link": { 7 | "type": "generated-index", 8 | "description": "Learn more about Secutils.dev tools for dealing with web security." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/project/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Project", 3 | "position": 1, 4 | "collapsed": false, 5 | "collapsible": false 6 | } 7 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/project/changelog/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Changelog", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/project/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: / 3 | sidebar_position: 1 4 | sidebar_label: What is Secutils.dev? 5 | --- 6 | 7 | # What is Secutils.dev? 8 | 9 | Secutils.dev is an [open-source](https://github.com/secutils-dev) toolbox that's versatile and simple, designed for both application security engineers and any other engineers looking to develop and test secure applications. 10 | 11 | The toolbox aims to simplify and streamline the process of developing and testing secure applications by providing a comprehensive collection of utilities commonly used by software engineers, all within a user-friendly and straightforward interface. 12 | 13 | If you have a question or idea, we encourage you to use the [Github Discussions](https://github.com/secutils-dev/secutils/discussions). For bug reports, please submit them directly to [Github Issues](https://github.com/secutils-dev/secutils/issues). If you need to contact us for anything else, feel free to do so using the "Contact" form. 14 | -------------------------------------------------------------------------------- /components/secutils-docs/docs/project/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Roadmap 4 | description: Explore the upcoming features coming to Secutils.dev soon. 5 | --- 6 | 7 | # Roadmap 8 | 9 | You can find the roadmap for Secutils.dev on [GitHub](https://github.com/orgs/secutils-dev/projects/1). 10 | -------------------------------------------------------------------------------- /components/secutils-docs/sidebars.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 4 | const sidebars = { 5 | blogSidebar: [{type: 'autogenerated', dirName: '.'}], 6 | }; 7 | 8 | module.exports = sidebars; 9 | -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-06_breakdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-06_breakdown.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-06_notion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-06_notion.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-06_roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-06_roadmap.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-08_csp_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-08_csp_create.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-08_csp_deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-08_csp_deploy.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-08_csp_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-08_csp_monitor.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-08_mdn_csp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-08_mdn_csp.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-13_portfolio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-13_portfolio.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-15_resources_trackers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-15_resources_trackers.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-20_readers_stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-20_readers_stat.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_web_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_web_bookmark.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_webhook_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_webhook_v1.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_webhook_v1_requests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_webhook_v1_requests.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_webhook_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_webhook_v2.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_webhook_v3_headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_webhook_v3_headers.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-23_webhook_v3_iframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-23_webhook_v3_iframe.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-27_time_management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-27_time_management.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-06-30_auto_responders_chat_gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-06-30_auto_responders_chat_gpt.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-07-11_web_page_weight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-07-11_web_page_weight.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-07-13_web_page_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-07-13_web_page_resources.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-07-18_web_page_resources_tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-07-18_web_page_resources_tracker.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-07-20_dara_knot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-07-20_dara_knot.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-07-27_phishing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-07-27_phishing.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-01_q3_2023_iteration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-01_q3_2023_iteration.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-08_scheduler_component_job_activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-08_scheduler_component_job_activity.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-08_scheduler_component_job_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-08_scheduler_component_job_create.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-15_cost_of_false_positives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-15_cost_of_false_positives.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-17_cost_of_false_positives_large_apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-17_cost_of_false_positives_large_apps.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-22_useful_newsletters_and_podcasts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-22_useful_newsletters_and_podcasts.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-08-29_best_application_security_tool_is_education.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-08-29_best_application_security_tool_is_education.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-09-05_q3_2023_update_notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-09-05_q3_2023_update_notifications.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-09-12_running_web_scraping_service_securely.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-09-12_running_web_scraping_service_securely.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-04_custom_resources_filtering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-04_custom_resources_filtering.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-04_email_notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-04_email_notifications.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-04_resources_trackers_enhancements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-04_resources_trackers_enhancements.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-04_scheduled_resource_checks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-04_scheduled_resource_checks.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-04_sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-04_sharing.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-10-10_subdomain_responders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-10-10_subdomain_responders.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-07_easy_rules_secure_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-07_easy_rules_secure_code.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_bing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_bing.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt_policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt_policy.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt_reporting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_chatgpt_reporting.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_dialog.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_duckduckgo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_duckduckgo.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2023-11-28_import_policy_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2023-11-28_import_policy_google.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker_preview.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-16_web_page_content_tracker_ui.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_execution_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_execution_time.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_memory.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_terminations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-01-24_rust_application_with_js_extensions_terminations.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_always_be_learning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_always_be_learning.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_assume_compromise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_assume_compromise.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_avoid_insecurity_through_obscurity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_avoid_insecurity_through_obscurity.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_be_pragmatic_about_security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_be_pragmatic_about_security.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_explore_unhappy_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_explore_unhappy_path.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-02-20_security_mindset_logs_and_exceptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-02-20_security_mindset_logs_and_exceptions.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_codeowners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_codeowners.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_codeowners_with_commits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_codeowners_with_commits.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/2024-06-11_open_source_intelligence_grafana_linkedin.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/elastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/elastic.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/goal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/goal.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/blog/plausible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/blog/plausible.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_certificates_curve_name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_certificates_curve_name.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_certificates_key_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_certificates_key_size.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_sharing.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_web_scraping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.3_web_scraping.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_content_trackers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_content_trackers.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_import_csp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_import_csp.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_private_keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_private_keys.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_responders_same_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_responders_same_path.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_responders_subdomain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_responders_subdomain.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_retries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_retries.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_share_certificate_templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_share_certificate_templates.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_tracker_headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_tracker_headers.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_trackers_indicators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_trackers_indicators.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_trackers_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.4_trackers_preview.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_client_socket_and_query_string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_client_socket_and_query_string.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_script.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_script_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_alpha.5_responders_script_indicator.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_beta.1_platform_account_management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_beta.1_platform_account_management.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/changelog_1.0.0_beta.1_responders_enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/changelog_1.0.0_beta.1_responders_enable.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/docs/guides_custom_tracker_schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/docs/guides_custom_tracker_schedule.png -------------------------------------------------------------------------------- /components/secutils-docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/img/favicon.ico -------------------------------------------------------------------------------- /components/secutils-docs/static/video/blog/2023-06-30_auto_responders_chat_gpt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/blog/2023-06-30_auto_responders_chat_gpt.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/blog/2023-06-30_auto_responders_chat_gpt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/blog/2023-06-30_auto_responders_chat_gpt.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_https_server.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_https_server.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_https_server.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_https_server.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_jwk_export.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_jwk_export.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_jwk_export.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_jwk_export.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_template_share.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_template_share.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_template_share.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_certificate_templates_template_share.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_private_keys_ecdsa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_private_keys_ecdsa.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_private_keys_ecdsa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_private_keys_ecdsa.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_private_keys_rsa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_private_keys_rsa.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/digital_certificates_private_keys_rsa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/digital_certificates_private_keys_rsa.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_content_tracker.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_content_tracker.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_content_tracker.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_content_tracker.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_content_tracker_diff.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_content_tracker_diff.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_content_tracker_diff.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_content_tracker_diff.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker_diff.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker_diff.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker_diff.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker_diff.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker_filter.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker_filter.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_scraping_resources_tracker_filter.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_scraping_resources_tracker_filter.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_import_policy_string.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_import_policy_string.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_import_policy_string.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_import_policy_string.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_import_policy_url.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_import_policy_url.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_import_policy_url.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_import_policy_url.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_new_policy.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_new_policy.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_new_policy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_new_policy.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_policy_share.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_policy_share.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_policy_share.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_policy_share.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_report_policy_violations.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_report_policy_violations.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_report_policy_violations.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_report_policy_violations.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_test_policy.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_test_policy.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/web_security_csp_test_policy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/web_security_csp_test_policy.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_dynamic_responder.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_dynamic_responder.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_dynamic_responder.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_dynamic_responder.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_html_responder.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_html_responder.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_html_responder.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_html_responder.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_json_responder.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_json_responder.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_json_responder.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_json_responder.webm -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_tracking_responder.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_tracking_responder.mp4 -------------------------------------------------------------------------------- /components/secutils-docs/static/video/guides/webhooks_tracking_responder.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-docs/static/video/guides/webhooks_tracking_responder.webm -------------------------------------------------------------------------------- /components/secutils-docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@docusaurus/tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/secutils-jwt-tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "secutils-jwt-tools" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0.97" 8 | clap = { version = "4.5.36", features = ["derive"] } 9 | env_logger = "0.11.8" 10 | humantime = "2.2.0" 11 | jsonwebtoken = {version = "9.3.1", default-features = false } 12 | log = "0.4.27" 13 | serde = "1.0.219" 14 | serde_derive = "1.0.219" 15 | serde_json = "1.0.140" 16 | time = "0.3.41" 17 | -------------------------------------------------------------------------------- /components/secutils-webui/.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@parcel/config-default", 3 | "compressors": { 4 | "*.{html,css,js,svg,map}": [ 5 | "...", 6 | "@parcel/compressor-gzip", 7 | "@parcel/compressor-brotli" 8 | ] 9 | }, 10 | "transformers": { 11 | "favicon.ico": ["@parcel/transformer-raw"], 12 | "*.{mp4,webm}": ["@parcel/transformer-raw"], 13 | "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"] 14 | }, 15 | "validators": { 16 | "*.{ts,tsx}": ["@parcel/validator-typescript"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/secutils-webui/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /components/secutils-webui/README.md: -------------------------------------------------------------------------------- 1 | The web user interface for Secutils.dev. 2 | 3 | ## Getting started 4 | 5 | Install all the required dependencies with `npm install` and run the UI in watch mode with `npm run watch`. 6 | 7 | ### Usage 8 | 9 | Assuming you already have a running [Secutils.dev server](https://github.com/secutils-dev/secutils) running at http://localhost:7070, the UI should be accessible at http://localhost:7171. 10 | -------------------------------------------------------------------------------- /components/secutils-webui/config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | listen [::]:8080; 4 | server_name localhost; 5 | 6 | gzip_static on; 7 | 8 | #access_log /var/log/nginx/host.access.log main; 9 | 10 | location / { 11 | root /usr/share/nginx/html; 12 | index index.html; 13 | add_header Cache-Control no-cache; 14 | # First attempt to serve request as file, then as directory, then fall back to redirecting to index.html 15 | try_files $uri $uri/ $uri.html /index.html; 16 | } 17 | 18 | # Don't serve API requests. 19 | location /api/ { 20 | return 404; 21 | } 22 | 23 | location ~* \.(?:jpg|jpeg|png|webp|gz|svg|svgz|mp4|webm|css|js|br|gz)$ { 24 | root /usr/share/nginx/html; 25 | expires 1y; 26 | access_log off; 27 | add_header Cache-Control "public"; 28 | } 29 | 30 | add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com"; 31 | } 32 | -------------------------------------------------------------------------------- /components/secutils-webui/src/app_container/app_context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import type { Dispatch } from 'react'; 3 | 4 | import type { UiState, UserSettings } from '../model'; 5 | import type { PageToast } from '../pages/page'; 6 | 7 | export interface AppContextValue { 8 | uiState: UiState; 9 | refreshUiState: () => void; 10 | settings?: UserSettings; 11 | setSettings: Dispatch; 12 | addToast: (toast: PageToast) => void; 13 | } 14 | 15 | export const AppContext = createContext(undefined); 16 | -------------------------------------------------------------------------------- /components/secutils-webui/src/app_container/index.ts: -------------------------------------------------------------------------------- 1 | export { AppContainer } from './app_container'; 2 | export { AppContext } from './app_context'; 3 | export type { AppContextValue } from './app_context'; 4 | export { SettingsFlyout } from './settings_flyout'; 5 | -------------------------------------------------------------------------------- /components/secutils-webui/src/assets.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const path: string; 3 | export default path; 4 | } 5 | 6 | declare module '*.webm' { 7 | const path: string; 8 | export default path; 9 | } 10 | 11 | declare module '*.mp4' { 12 | const path: string; 13 | export default path; 14 | } 15 | 16 | declare module '@elastic/eui/es/components/icon/icon' { 17 | import type { SVGProps } from 'react'; 18 | export function appendIconComponentCache(icons: Record>): void; 19 | } 20 | 21 | declare module '@elastic/eui/es/components/icon/assets/*' { 22 | import type { SVGProps } from 'react'; 23 | export const icon: SVGProps; 24 | } 25 | 26 | declare module '@elastic/eui/dist/*.min.css' { 27 | const path: string; 28 | export default path; 29 | } 30 | 31 | declare module 'url:monaco-editor/*' { 32 | const path: string; 33 | export default path; 34 | } 35 | -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/favicon.ico -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/mstile-144x144.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/mstile-150x150.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/mstile-310x150.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/mstile-310x310.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/assets/mstile-70x70.png -------------------------------------------------------------------------------- /components/secutils-webui/src/assets/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "./android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { PageLoadingState, type PageLoadingStateProps } from './page_loading_state'; 2 | export { PageErrorState, type PageErrorStateProps } from './page_error_state'; 3 | export { PageSuccessState, type PageSuccessStateProps } from './page_success_state'; 4 | export { PageUnderConstructionState } from './page_under_construction_state'; 5 | export { Logo } from './logo'; 6 | export { LogoWithName } from './logo_with_name'; 7 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/page_error_state.tsx: -------------------------------------------------------------------------------- 1 | import { EuiIcon, EuiLink } from '@elastic/eui'; 2 | import { useCallback } from 'react'; 3 | 4 | import { PageState, type PageStateProps } from './page_state'; 5 | 6 | export type PageErrorStateProps = Omit; 7 | 8 | export function PageErrorState({ title, content, action }: PageErrorStateProps) { 9 | const onPageRefresh = useCallback(() => { 10 | window.location.reload(); 11 | }, []); 12 | 13 | const actionNode = action ? ( 14 | action 15 | ) : ( 16 |

17 | Refresh the page 18 |

19 | ); 20 | 21 | return ( 22 | } 28 | /> 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/page_loading_state.tsx: -------------------------------------------------------------------------------- 1 | import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingLogo } from '@elastic/eui'; 2 | 3 | import { Logo } from './logo'; 4 | 5 | export interface PageLoadingStateProps { 6 | title?: string; 7 | } 8 | 9 | export function PageLoadingState({ title = 'Loading…' }: PageLoadingStateProps) { 10 | return ( 11 | 18 | 19 | } size="l" />} 21 | titleSize="xs" 22 | title={

{title}

} 23 | /> 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/page_state.tsx: -------------------------------------------------------------------------------- 1 | import type { BACKGROUND_COLORS } from '@elastic/eui'; 2 | import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; 3 | import type { ReactNode } from 'react'; 4 | 5 | export interface PageStateProps { 6 | title: string; 7 | color?: (typeof BACKGROUND_COLORS)[number]; 8 | icon?: ReactNode; 9 | content?: ReactNode; 10 | action?: ReactNode; 11 | } 12 | 13 | export function PageState({ title, content = null, action, color, icon }: PageStateProps) { 14 | return ( 15 | 22 | 23 | {title}} 27 | titleSize="s" 28 | body={ 29 |
30 | {content} 31 | {action} 32 |
33 | } 34 | /> 35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/page_success_state.tsx: -------------------------------------------------------------------------------- 1 | import { EuiIcon } from '@elastic/eui'; 2 | 3 | import { PageState, type PageStateProps } from './page_state'; 4 | 5 | export type PageSuccessStateProps = Omit; 6 | 7 | export function PageSuccessState({ title, content, action }: PageSuccessStateProps) { 8 | return ( 9 | } 15 | /> 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/secutils-webui/src/components/page_under_construction_state.tsx: -------------------------------------------------------------------------------- 1 | import { PageLoadingState } from './page_loading_state'; 2 | 3 | export function PageUnderConstructionState() { 4 | return ; 5 | } 6 | 7 | export default PageUnderConstructionState; 8 | -------------------------------------------------------------------------------- /components/secutils-webui/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secutils-dev/secutils/1848a621cb9ed477d1853505dc7fd0b1ccf96a5c/components/secutils-webui/src/favicon.ico -------------------------------------------------------------------------------- /components/secutils-webui/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useLocalStorage } from './use_local_storage'; 2 | export { usePageMeta } from './use_page_meta'; 3 | export { useAppContext } from './use_app_context'; 4 | export { useRangeTicks } from './use_range_ticks'; 5 | export { usePageHeaderActions } from './page_header_actions'; 6 | -------------------------------------------------------------------------------- /components/secutils-webui/src/hooks/use_app_context.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { AppContext } from '../app_container'; 4 | 5 | export function useAppContext() { 6 | const appContext = useContext(AppContext); 7 | if (!appContext) { 8 | throw new Error('App context provider is not found.'); 9 | } 10 | 11 | return appContext; 12 | } 13 | -------------------------------------------------------------------------------- /components/secutils-webui/src/hooks/use_local_storage.ts: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | export function useLocalStorage(key: string, defaultValue: TValue) { 5 | const [storedValue, setStoredValue] = useState(() => { 6 | try { 7 | const item = window.localStorage.getItem(key); 8 | return item ? (JSON.parse(item) as TValue) : defaultValue; 9 | } catch (err) { 10 | console.error(err); 11 | return defaultValue; 12 | } 13 | }); 14 | 15 | useEffect(() => { 16 | try { 17 | if (storedValue != null) { 18 | window.localStorage.setItem(key, JSON.stringify(storedValue)); 19 | } else { 20 | window.localStorage.removeItem(key); 21 | } 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | }, [storedValue, key]); 26 | 27 | return [storedValue, setStoredValue] as [TValue, Dispatch>]; 28 | } 29 | -------------------------------------------------------------------------------- /components/secutils-webui/src/hooks/use_page_meta.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | export function usePageMeta(pageTitle: string) { 4 | useEffect(() => { 5 | document.title = `Secutils.dev - ${pageTitle}`; 6 | }, [pageTitle]); 7 | } 8 | -------------------------------------------------------------------------------- /components/secutils-webui/src/hooks/use_range_ticks.ts: -------------------------------------------------------------------------------- 1 | import { useIsWithinMaxBreakpoint } from '@elastic/eui'; 2 | 3 | export function useRangeTicks() { 4 | const isWithinMaxBreakpoint = useIsWithinMaxBreakpoint('xs'); 5 | // Determines the maximum value for the range to show ticks. 6 | return isWithinMaxBreakpoint ? 10 : 15; 7 | } 8 | -------------------------------------------------------------------------------- /components/secutils-webui/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap'); 2 | 3 | .root { 4 | display: flex; 5 | height: 100vh; 6 | padding-top: 48px; 7 | } 8 | 9 | .euiToastHeader { 10 | align-items: center; 11 | } 12 | 13 | .euiOverlayMask[data-relative-to-header='below'] { 14 | top: 0; 15 | } 16 | 17 | /* Bug fix for v82.2.0 */ 18 | .root .euiDataGridRowCell__actionButtonIcon { 19 | inline-size: 12px; 20 | block-size: 12px; 21 | } 22 | 23 | .signin-form, .signup-form, .password-form { 24 | width: 280px; 25 | } 26 | -------------------------------------------------------------------------------- /components/secutils-webui/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Secutils.dev - Ultimate toolbox for application security engineers 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/async_data.ts: -------------------------------------------------------------------------------- 1 | export type AsyncData = 2 | | { status: 'pending'; state?: TState } 3 | | { status: 'failed'; error: string; state?: TState } 4 | | { status: 'succeeded'; data: TValue; state?: TState }; 5 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/errors.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError } from 'axios'; 2 | import { CanceledError } from 'axios'; 3 | 4 | export function isAbortError(err: unknown) { 5 | return err instanceof CanceledError || (err instanceof DOMException && err.name === 'AbortError'); 6 | } 7 | 8 | export function getErrorMessage(err: unknown) { 9 | return (isApplicationError(err) ? err.response?.data.message : undefined) ?? (err as Error).message; 10 | } 11 | 12 | export function isClientError(err: unknown) { 13 | const status = getErrorStatus(err); 14 | return status ? status >= 400 && status < 500 : false; 15 | } 16 | 17 | export function getErrorStatus(err: unknown) { 18 | return (err as AxiosError).response?.status ?? (err as { status?: number }).status; 19 | } 20 | 21 | function isApplicationError(err: unknown): err is AxiosError<{ message: string }> { 22 | const forceCastedError = err as AxiosError<{ message: string }>; 23 | return forceCastedError.isAxiosError && !!forceCastedError.response?.data?.message; 24 | } 25 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/search_item.ts: -------------------------------------------------------------------------------- 1 | export interface SerializedSearchItem { 2 | l: string; 3 | c: string; 4 | s?: string; 5 | m?: Record; 6 | t: number; 7 | } 8 | 9 | export interface SearchItem { 10 | label: string; 11 | category: string; 12 | subCategory?: string; 13 | meta?: Record; 14 | timestamp: number; 15 | } 16 | export function deserializeSearchItem(searchItem: SerializedSearchItem): SearchItem { 17 | return { 18 | label: searchItem.l, 19 | category: searchItem.c, 20 | subCategory: searchItem.s, 21 | meta: searchItem.m, 22 | timestamp: searchItem.t, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/server_status.ts: -------------------------------------------------------------------------------- 1 | export interface ServerStatus { 2 | level: 'available' | 'unavailable'; 3 | } 4 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/urls.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig } from 'axios'; 2 | 3 | import { getUserShareId, USER_SHARE_ID_HEADER_NAME } from './user_share'; 4 | 5 | /** 6 | * Takes API URL path and returns it back with any environment modifications if needed. 7 | * @param path API endpoint relative path. 8 | */ 9 | export function getApiUrl(path: string) { 10 | return path; 11 | } 12 | 13 | export function getApiRequestConfig(): AxiosRequestConfig | undefined { 14 | const shareId = getUserShareId(); 15 | return shareId ? { headers: { [USER_SHARE_ID_HEADER_NAME]: shareId } } : undefined; 16 | } 17 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/user.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { getApiUrl } from './urls'; 4 | import type { UserSubscription } from './user_subscription'; 5 | 6 | export interface User { 7 | email: string; 8 | handle: string; 9 | isActivated: boolean; 10 | isOperator?: boolean; 11 | subscription: UserSubscription; 12 | } 13 | 14 | export async function getUserData(dataNamespace: string) { 15 | const response = await axios.get<{ [namespace: string]: unknown }>( 16 | getApiUrl(`/api/user/data?namespace=${dataNamespace}`), 17 | ); 18 | return response.data[dataNamespace] as RType | null; 19 | } 20 | 21 | export async function setUserData(dataNamespace: string, dataValue: unknown) { 22 | const response = await axios.post<{ [namespace: string]: unknown }>( 23 | getApiUrl(`/api/user/data?namespace=${dataNamespace}`), 24 | { dataValue: JSON.stringify(dataValue) }, 25 | ); 26 | return response.data[dataNamespace] as RType | null; 27 | } 28 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/user_settings.ts: -------------------------------------------------------------------------------- 1 | export const USER_SETTINGS_USER_DATA_TYPE = 'userSettings'; 2 | export const USER_SETTINGS_KEY_COMMON_SHOW_ONLY_FAVORITES = 'common.showOnlyFavorites'; 3 | export const USER_SETTINGS_KEY_COMMON_FAVORITES = 'common.favorites'; 4 | export const USER_SETTINGS_KEY_COMMON_UI_THEME = 'common.uiTheme'; 5 | 6 | export type UserSettings = Record; 7 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/user_share.ts: -------------------------------------------------------------------------------- 1 | export const USER_SHARE_ID_HEADER_NAME = 'x-user-share-id'; 2 | 3 | export function getUserShareId() { 4 | return new URLSearchParams(window.location.search).get(USER_SHARE_ID_HEADER_NAME); 5 | } 6 | 7 | export function removeUserShareId() { 8 | const searchParams = new URLSearchParams(window.location.search); 9 | if (searchParams.has(USER_SHARE_ID_HEADER_NAME)) { 10 | searchParams.delete(USER_SHARE_ID_HEADER_NAME); 11 | window.history.replaceState( 12 | null, 13 | '', 14 | searchParams.size > 0 ? `${window.location.pathname}?${searchParams.toString()}` : window.location.pathname, 15 | ); 16 | } 17 | } 18 | 19 | /** 20 | * Describes a user share. 21 | */ 22 | export interface UserShare { 23 | id: string; 24 | resource: UserShareResource; 25 | createdAt: number; 26 | } 27 | 28 | /** 29 | * Describes a resource that can be shared with other users. 30 | */ 31 | export type UserShareResource = 32 | | { 33 | type: 'contentSecurityPolicy'; 34 | policyId: string; 35 | } 36 | | { 37 | type: 'certificateTemplate'; 38 | templateId: string; 39 | }; 40 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/user_subscription.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * User subscription model. 3 | */ 4 | export interface UserSubscription { 5 | tier: 'basic' | 'standard' | 'professional' | 'ultimate'; 6 | /** 7 | * Indicates since when the subscription is active. 8 | */ 9 | startedAt: number; 10 | /** 11 | * Indicates when the subscription ends. 12 | */ 13 | endsAt?: number; 14 | /** 15 | * Indicates when the trial started. 16 | */ 17 | trialStartedAt?: number; 18 | /** 19 | * Indicates when the trial ends. 20 | */ 21 | trialEndsAt?: number; 22 | } 23 | -------------------------------------------------------------------------------- /components/secutils-webui/src/model/util.ts: -------------------------------------------------------------------------------- 1 | export interface Util { 2 | handle: string; 3 | name: string; 4 | utils?: Util[]; 5 | } 6 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/activate/index.ts: -------------------------------------------------------------------------------- 1 | import { ActivatePage } from './activate_page'; 2 | 3 | export default ActivatePage; 4 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export { WorkspacePage } from './workspace'; 2 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/page_header.tsx: -------------------------------------------------------------------------------- 1 | import { EuiPageHeader, EuiPageSection, useEuiTheme } from '@elastic/eui'; 2 | import { css } from '@emotion/react'; 3 | import type { ReactNode } from 'react'; 4 | 5 | export interface PageHeaderProps { 6 | title: ReactNode; 7 | } 8 | 9 | export function PageHeader({ title }: PageHeaderProps) { 10 | const theme = useEuiTheme(); 11 | 12 | return ( 13 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/signin/index.ts: -------------------------------------------------------------------------------- 1 | import { SigninPage } from './signin_page'; 2 | 3 | export default SigninPage; 4 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/signup/index.ts: -------------------------------------------------------------------------------- 1 | import { SignupPage } from './signup_page'; 2 | 3 | export default SignupPage; 4 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/components/help_page_content.tsx: -------------------------------------------------------------------------------- 1 | import { EuiFlexGroup } from '@elastic/eui'; 2 | import { css } from '@emotion/react'; 3 | import type { ReactNode } from 'react'; 4 | 5 | import { useFontSizes } from '../hooks'; 6 | 7 | export interface Props { 8 | children: ReactNode; 9 | } 10 | 11 | export default function HelpPageContent({ children }: Props) { 12 | const fontSizes = useFontSizes(); 13 | const pageStyle = css` 14 | ${fontSizes.text} 15 | width: 100%; 16 | padding: 1% 5% 0; 17 | `; 18 | return ( 19 | 20 | {children} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/components/styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | 3 | export const GUIDE_CARD_STYLE = css` 4 | flex: 1; 5 | `; 6 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useWorkspaceContext } from './use_workspace_context'; 2 | export { useScrollToHash } from './use_scroll_to_hash'; 3 | export { useFontSizes } from './use_font_sizes'; 4 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/hooks/use_font_sizes.ts: -------------------------------------------------------------------------------- 1 | import { useEuiFontSize, useIsWithinMaxBreakpoint } from '@elastic/eui'; 2 | 3 | export function useFontSizes() { 4 | const isWithinMaxBreakpoint = useIsWithinMaxBreakpoint('l'); 5 | 6 | return { 7 | text: useEuiFontSize(isWithinMaxBreakpoint ? 'm' : 'l'), 8 | codeSample: isWithinMaxBreakpoint ? ('m' as const) : ('l' as const), 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/hooks/use_scroll_to_hash.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useLocation } from 'react-router'; 3 | 4 | // Height of the website fixed header. 5 | const FIXED_HEADER_HEIGHT = 48; 6 | 7 | export function useScrollToHash(delay = 250) { 8 | const location = useLocation(); 9 | 10 | useEffect(() => { 11 | const elementToScroll = document.getElementById(location.hash.replace('#', '')); 12 | if (elementToScroll) { 13 | setTimeout(() => { 14 | window.scrollTo({ top: elementToScroll.offsetTop - FIXED_HEADER_HEIGHT, behavior: 'smooth' }); 15 | }, delay); 16 | } 17 | }, [delay, location.hash]); 18 | } 19 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/hooks/use_workspace_context.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import { useAppContext } from '../../../hooks'; 4 | import { WorkspaceContext } from '../workspace_context'; 5 | 6 | export function useWorkspaceContext() { 7 | const appContext = useAppContext(); 8 | 9 | const workspaceContext = useContext(WorkspaceContext); 10 | if (!workspaceContext) { 11 | throw new Error('Workspace context provider is not found.'); 12 | } 13 | 14 | return { ...appContext, ...workspaceContext }; 15 | } 16 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/index.ts: -------------------------------------------------------------------------------- 1 | export { WorkspacePage } from './workspace_page'; 2 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/certificate_lifetime_calendar.tsx: -------------------------------------------------------------------------------- 1 | import { EuiDatePicker } from '@elastic/eui'; 2 | import type { Moment } from 'moment'; 3 | import { unix } from 'moment'; 4 | import { useCallback, useState } from 'react'; 5 | 6 | export interface CertificateLifetimeCalendarProps { 7 | isDisabled?: boolean; 8 | currentTimestamp: number; 9 | onChange(timestamp: number): void; 10 | } 11 | 12 | export function CertificateLifetimeCalendar({ 13 | onChange, 14 | currentTimestamp, 15 | isDisabled = false, 16 | }: CertificateLifetimeCalendarProps) { 17 | const [selectedDate, setSelectedDate] = useState(unix(currentTimestamp)); 18 | const onSelectedDateChange = useCallback( 19 | (selectedDate: Moment | null) => { 20 | setSelectedDate(selectedDate); 21 | 22 | if (selectedDate) { 23 | onChange(selectedDate.unix()); 24 | } 25 | }, 26 | [onChange], 27 | ); 28 | 29 | return ( 30 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/certificate_template.ts: -------------------------------------------------------------------------------- 1 | import type { CertificateAttributes } from './certificate_attributes'; 2 | 3 | export interface CertificateTemplate { 4 | id: string; 5 | name: string; 6 | attributes: CertificateAttributes; 7 | createdAt: number; 8 | updatedAt: number; 9 | } 10 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/consts.ts: -------------------------------------------------------------------------------- 1 | export const SELF_SIGNED_PROD_WARNING_USER_SETTINGS_KEY = 'certificates.doNotShowSelfSignedWarning'; 2 | export const PRIVATE_KEYS_PROD_WARNING_USER_SETTINGS_KEY = 'certificates.doNotShowPrivateKeysWarning'; 3 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/encryption_mode.ts: -------------------------------------------------------------------------------- 1 | export type EncryptionMode = 'none' | 'passphrase'; 2 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/private_key.ts: -------------------------------------------------------------------------------- 1 | import type { PrivateKeyAlgorithm } from './private_key_alg'; 2 | 3 | // Describes an instance of a private key. 4 | export interface PrivateKey { 5 | id: string; 6 | name: string; 7 | alg: PrivateKeyAlgorithm; 8 | encrypted: boolean; 9 | createdAt: number; 10 | updatedAt: number; 11 | } 12 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/certificates/private_key_alg.ts: -------------------------------------------------------------------------------- 1 | export type PrivateKeyAlgorithm = 2 | | { keyType: 'ed25519' } 3 | | { keyType: 'rsa' | 'dsa'; keySize: PrivateKeySize } 4 | | { keyType: 'ecdsa'; curve: PrivateKeyCurveName }; 5 | 6 | export type PrivateKeySize = '1024' | '2048' | '4096' | '8192'; 7 | export type PrivateKeyCurveName = 'secp256r1' | 'secp384r1' | 'secp521r1'; 8 | 9 | export function privateKeyAlgString(alg: PrivateKeyAlgorithm) { 10 | switch (alg.keyType) { 11 | case 'rsa': 12 | case 'dsa': 13 | return `${alg.keyType.toUpperCase()} (${alg.keySize} bits)`; 14 | case 'ecdsa': 15 | return `ECDSA (${privateKeyCurveNameString(alg.curve)})`; 16 | default: 17 | return 'Ed25519 (256 bits)'; 18 | } 19 | } 20 | 21 | export function privateKeyCurveNameString(curve: PrivateKeyCurveName) { 22 | switch (curve) { 23 | case 'secp256r1': 24 | return 'prime256v1 / secp256r1 / NIST P-256'; 25 | case 'secp384r1': 26 | return 'secp384r1 / NIST P-384'; 27 | case 'secp521r1': 28 | return 'secp521r1 / NIST P-521'; 29 | default: 30 | return curve; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/web_scraping/web_page_data_revision.ts: -------------------------------------------------------------------------------- 1 | import type { WebPageResource } from './web_page_resource'; 2 | 3 | export interface WebPageDataRevision { 4 | id: string; 5 | data: D; 6 | createdAt: number; 7 | } 8 | 9 | export interface WebPageResourcesRevision 10 | extends WebPageDataRevision<{ 11 | scripts?: WebPageResource[]; 12 | styles?: WebPageResource[]; 13 | }> {} 14 | 15 | export interface WebPageContentRevision extends WebPageDataRevision {} 16 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/web_scraping/web_page_resource.ts: -------------------------------------------------------------------------------- 1 | export interface WebPageResource { 2 | url?: string; 3 | content?: WebPageResourceContent; 4 | diffStatus?: 'added' | 'removed' | 'changed'; 5 | } 6 | 7 | export interface WebPageResourceContent { 8 | digest: string; 9 | size: number; 10 | } 11 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/web_scraping/web_page_tracker.ts: -------------------------------------------------------------------------------- 1 | export interface WebPageTracker { 2 | id: string; 3 | name: string; 4 | url: string; 5 | createdAt: number; 6 | updatedAt: number; 7 | settings: { 8 | revisions: number; 9 | delay: number; 10 | schedule?: string; 11 | scripts?: S; 12 | headers?: Record; 13 | }; 14 | jobConfig?: SchedulerJobConfig; 15 | } 16 | 17 | export interface SchedulerJobConfig { 18 | schedule: string; 19 | retryStrategy?: SchedulerJobRetryStrategy; 20 | notifications: boolean; 21 | } 22 | 23 | export interface SchedulerJobRetryStrategy { 24 | type: 'constant'; 25 | interval: number; 26 | maxAttempts: number; 27 | } 28 | 29 | export interface WebPageResourcesTracker 30 | extends WebPageTracker<{ 31 | resourceFilterMap?: string; 32 | }> {} 33 | 34 | export interface WebPageContentTracker 35 | extends WebPageTracker<{ 36 | extractContent?: string; 37 | }> {} 38 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/web_scraping/web_page_tracker_name.tsx: -------------------------------------------------------------------------------- 1 | import { EuiIcon, EuiText } from '@elastic/eui'; 2 | 3 | import type { WebPageTracker } from './web_page_tracker'; 4 | 5 | export function WebPageTrackerName({ tracker }: { tracker: WebPageTracker }) { 6 | if (!tracker.jobConfig) { 7 | return tracker.name; 8 | } 9 | 10 | const timeIcon = ; 11 | return tracker.jobConfig.notifications ? ( 12 | 13 | {tracker.name} {timeIcon} 14 | 15 | ) : ( 16 | 17 | {tracker.name} {timeIcon} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/webhooks/responder.ts: -------------------------------------------------------------------------------- 1 | export interface Responder { 2 | id: string; 3 | name: string; 4 | location: { 5 | pathType: '=' | '^'; 6 | path: string; 7 | subdomainPrefix?: string; 8 | }; 9 | method: string; 10 | enabled: boolean; 11 | settings: { 12 | requestsToTrack: number; 13 | statusCode: number; 14 | headers?: Array<[string, string]>; 15 | body?: string; 16 | script?: string; 17 | }; 18 | createdAt: number; 19 | updatedAt: number; 20 | } 21 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/webhooks/responder_name.tsx: -------------------------------------------------------------------------------- 1 | import { EuiIcon, EuiText, useEuiTheme } from '@elastic/eui'; 2 | 3 | import type { Responder } from './responder'; 4 | 5 | export function ResponderName({ responder }: { responder: Responder }) { 6 | const theme = useEuiTheme(); 7 | if (!responder.settings.script && responder.enabled) { 8 | return responder.name; 9 | } 10 | 11 | const disabledIcon = ; 12 | if (responder.settings.script) { 13 | const scriptIcon = ; 14 | return responder.enabled ? ( 15 | 16 | {responder.name} {scriptIcon} 17 | 18 | ) : ( 19 | 20 | {responder.name} {scriptIcon} {disabledIcon} 21 | 22 | ); 23 | } 24 | 25 | return ( 26 | 27 | {responder.name} {disabledIcon} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/webhooks/responder_request.ts: -------------------------------------------------------------------------------- 1 | export interface ResponderRequest { 2 | id: string; 3 | clientAddress?: string; 4 | method: string; 5 | headers?: Array<[string, number[]]>; 6 | url: string; 7 | body?: number[]; 8 | createdAt: number; 9 | } 10 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/utils/webhooks/responder_stats.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the stats of a responder. 3 | */ 4 | export interface ResponderStats { 5 | responderId: string; 6 | requestCount: number; 7 | lastRequestedAt?: number; 8 | } 9 | -------------------------------------------------------------------------------- /components/secutils-webui/src/pages/workspace/workspace_context.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { createContext } from 'react'; 3 | 4 | export interface WorkspaceContextValue { 5 | setTitleActions: (actions: ReactNode) => void; 6 | setTitle: (title: string) => void; 7 | } 8 | 9 | export const WorkspaceContext = createContext(undefined); 10 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/downloader.ts: -------------------------------------------------------------------------------- 1 | export class Downloader { 2 | static download(name: string, content: string | Uint8Array, type: string) { 3 | const downloadLink = document.createElement('a'); 4 | downloadLink.href = window.URL.createObjectURL(new Blob([content], { type })); 5 | downloadLink.setAttribute('download', name); 6 | 7 | document.body.appendChild(downloadLink); 8 | downloadLink.click(); 9 | document.body.removeChild(downloadLink); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/monaco/editor.worker.ts: -------------------------------------------------------------------------------- 1 | import 'monaco-editor/esm/vs/editor/editor.worker.js'; 2 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/monaco/ts.worker.ts: -------------------------------------------------------------------------------- 1 | import 'monaco-editor/esm/vs/language/typescript/ts.worker.js'; 2 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/ory.ts: -------------------------------------------------------------------------------- 1 | export async function getOryApi() { 2 | return await import('@ory/client').then( 3 | ({ Configuration, FrontendApi }) => new FrontendApi(new Configuration({ basePath: location.origin })), 4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/url.ts: -------------------------------------------------------------------------------- 1 | // Verifies that the `next` URL is safe to redirect to (doesn't trick user into being redirected to a malicious site). 2 | export function isSafeNextUrl(urlString: string) { 3 | const origin = window.location.origin; 4 | try { 5 | return new URL(urlString, origin).origin === origin; 6 | } catch { 7 | return false; 8 | } 9 | } 10 | 11 | export function isValidURL(url: string) { 12 | try { 13 | new URL(url); 14 | return true; 15 | } catch { 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/secutils-webui/src/tools/webauthn.ts: -------------------------------------------------------------------------------- 1 | export function arrayBufferToSafeBase64Url(buffer: ArrayBuffer) { 2 | const array = new Uint8Array(buffer); 3 | 4 | let string = ''; 5 | for (let i = 0; i < array.byteLength; i++) { 6 | string += String.fromCharCode(array[i]); 7 | } 8 | 9 | return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/g, ''); 10 | } 11 | 12 | export function safeBase64UrlToArrayBuffer(base64Url: string): ArrayBuffer { 13 | const base64 = atob(base64Url.replace(/-/g, '+').replace(/_/g, '/')); 14 | const bytes = new Uint8Array(base64.length); 15 | for (let i = 0; i < base64.length; i++) { 16 | bytes[i] = base64.charCodeAt(i); 17 | } 18 | 19 | return bytes; 20 | } 21 | 22 | export function isWebAuthnSupported() { 23 | return window.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === 'function'; 24 | } 25 | -------------------------------------------------------------------------------- /components/secutils-webui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "jsxImportSource": "@emotion/react", 5 | "allowSyntheticDefaultImports": true, 6 | "isolatedModules": true, 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "lib": [ 11 | "dom", 12 | "dom.iterable", 13 | "es5", 14 | "es2015", 15 | "es2019", 16 | "es2020", 17 | ], 18 | "typeRoots": ["./src/assets.d.ts"] 19 | }, 20 | "include": [ 21 | "src/**/*" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /dev/api/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "local-api": { 3 | "host": "http://127.0.0.1:7070", 4 | "kratosHost": "http://127.0.0.1:4433", 5 | "kratosAdminHost": "http://127.0.0.1:4434" 6 | }, 7 | "dev-api": { 8 | "host": "https://dev.secutils.dev" 9 | }, 10 | "dev-webhooks-api": { 11 | "host": "https://__demo__.webhooks.dev.secutils.dev" 12 | }, 13 | "production-api": { 14 | "host": "https://secutils.dev" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dev/api/misc/send_message.http: -------------------------------------------------------------------------------- 1 | ### Signup user 2 | POST {{host}}/api/send_message 3 | Accept: application/json 4 | Content-Type: application/json 5 | 6 | { 7 | "message": "message", 8 | "email": "dev@secutils.dev" 9 | } 10 | -------------------------------------------------------------------------------- /dev/api/scheduler/parse_schedule.http: -------------------------------------------------------------------------------- 1 | ### Parse cron-like schedule (too little interval). 2 | # @no-cookie-jar 3 | POST {{host}}/api/scheduler/parse_schedule 4 | Accept: application/json 5 | Content-Type: application/json 6 | Cookie: {{cookie-credentials}} 7 | 8 | { 9 | "schedule": "* * * * * *" 10 | } 11 | 12 | ### Parse cron-like schedule (every Sunday). 13 | ### // sec min hour day of month month day of week year 14 | ### let expression = "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2"; 15 | # @no-cookie-jar 16 | POST {{host}}/api/scheduler/parse_schedule 17 | Accept: application/json 18 | Content-Type: application/json 19 | Cookie: {{cookie-credentials}} 20 | 21 | { 22 | "schedule": "0 * 9,12,15 1,15 * *" 23 | } 24 | -------------------------------------------------------------------------------- /dev/api/search/search.http: -------------------------------------------------------------------------------- 1 | # Site-wide search 2 | # @no-cookie-jar 3 | POST {{host}}/api/search 4 | Accept: application/json 5 | Content-Type: application/json 6 | 7 | { 8 | "query": "responder" 9 | } 10 | -------------------------------------------------------------------------------- /dev/api/security/users_remove.http: -------------------------------------------------------------------------------- 1 | ### Remove user 2 | POST {{host}}/api/users/remove 3 | Accept: application/json 4 | Content-Type: application/json 5 | Authorization: {{api-credentials}} 6 | 7 | { 8 | "email": "test@secutils.dev" 9 | } 10 | -------------------------------------------------------------------------------- /dev/api/security/users_signup.http: -------------------------------------------------------------------------------- 1 | ### Signup user 2 | POST http://127.0.0.1:7070/api/signup 3 | Accept: application/json 4 | Content-Type: application/json 5 | 6 | { 7 | "id": "27b6bd98-955d-4f30-afea-03818d8eaa71", 8 | "email": "dev@secutils.dev" 9 | } 10 | -------------------------------------------------------------------------------- /dev/api/ui/state_get.http: -------------------------------------------------------------------------------- 1 | ### Get parameters (authenticated) 2 | GET {{host}}/api/ui/state 3 | Accept: application/json 4 | Authorization: {{api-credentials}} 5 | 6 | ### Get parameters (authenticated via cookie) 7 | GET {{host}}/api/ui/state 8 | Accept: application/json 9 | Cookie: {{cookie-credentials}} 10 | 11 | ### Get parameters (unauthenticated) 12 | GET {{host}}/api/ui/state 13 | Accept: application/json 14 | -------------------------------------------------------------------------------- /dev/api/user/get.http: -------------------------------------------------------------------------------- 1 | ### Get user information 2 | GET {{host}}/api/users/1 3 | Accept: application/json 4 | Authorization: {{api-credentials}} 5 | -------------------------------------------------------------------------------- /dev/api/user/get_by_email.http: -------------------------------------------------------------------------------- 1 | ### Get user information 2 | GET {{host}}/api/users?email=su@secutils.dev 3 | Accept: application/json 4 | Authorization: {{api-credentials}} 5 | -------------------------------------------------------------------------------- /dev/api/user/get_data.http: -------------------------------------------------------------------------------- 1 | ### Get data 2 | GET {{host}}/api/user/data?namespace=userSettings 3 | Accept: application/json 4 | Authorization: {{api-credentials}} 5 | 6 | ### Get utils data 7 | GET {{host}}/api/user/data?namespace=webPageResourcesTrackers 8 | Accept: application/json 9 | Authorization: {{api-credentials}} 10 | -------------------------------------------------------------------------------- /dev/api/user/get_self.http: -------------------------------------------------------------------------------- 1 | ### Get user information 2 | GET {{host}}/api/users/self 3 | Accept: application/json 4 | Authorization: {{api-credentials}} 5 | -------------------------------------------------------------------------------- /dev/api/user/set_data.http: -------------------------------------------------------------------------------- 1 | ### Update data 2 | POST {{host}}/api/user/data?namespace=userSettings 3 | Accept: application/json 4 | Content-Type: application/json 5 | Authorization: {{api-credentials}} 6 | 7 | { 8 | "dataValue": "{ \"certificates.doNotShowSelfSignedWarning\": true }" 9 | } 10 | 11 | ### Remove data 12 | POST {{host}}/api/user/data?namespace=userSettings 13 | Accept: application/json 14 | Content-Type: application/json 15 | Authorization: {{api-credentials}} 16 | 17 | { 18 | "dataValue": "{ \"certificates.doNotShowSelfSignedWarning\": null }" 19 | } 20 | -------------------------------------------------------------------------------- /dev/api/utils/web_scraping_content.http: -------------------------------------------------------------------------------- 1 | ### Create web page resources tracker 2 | POST {{host}}/api/utils/web_scraping/content 3 | Authorization: {{api-credentials}} 4 | Accept: application/json 5 | Content-Type: application/json 6 | 7 | { 8 | "name": "HackerNewsDemo", 9 | "url": "https://news.ycombinator.com/", 10 | "settings": { 11 | "revisions": 1, 12 | "delay": 5000, 13 | "enableNotifications": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dev/api/utils/web_scraping_resources.http: -------------------------------------------------------------------------------- 1 | ### Create web page resources tracker 2 | POST {{host}}/api/utils/action 3 | Authorization: {{api-credentials}} 4 | Accept: application/json 5 | Content-Type: application/json 6 | 7 | { 8 | "action": { 9 | "type": "webScraping", 10 | "value": { 11 | "type": "saveWebPageResourcesTracker", 12 | "value": { 13 | "tracker": { 14 | "name": "HackerNewsDemo", 15 | "url": "https://news.ycombinator.com/", 16 | "revisions": 3, 17 | "delay": 5000, 18 | "schedule": "0 0 * * * * *", 19 | "scripts": { 20 | "resourceFilterMap": "return resource.type === 'script' \n ? resource\n : null;" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | ### Fetch web page resources 29 | POST {{host}}/api/utils/action 30 | Authorization: {{api-credentials}} 31 | Accept: application/json 32 | Content-Type: application/json 33 | 34 | { 35 | "action": { 36 | "type": "webScraping", 37 | "value": { 38 | "type": "fetchWebPageResources", 39 | "value": { "trackerName": "HackerNewsDemo", "refresh": true } 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /dev/api/utils/webhooks.http: -------------------------------------------------------------------------------- 1 | ### Test webhook (path). 2 | GET {{host}}/api/webhooks/su/a 3 | Accept: application/json 4 | Content-Type: application/json 5 | 6 | ### Test webhook (subdomain). 7 | GET {{host}}/api/webhooks 8 | Accept: application/json 9 | Content-Type: application/json 10 | X-Forwarded-Host: su.{{host}} 11 | X-Replaced-Path: /a 12 | 13 | ### Clear responder requests history. 14 | POST {{host}}/api/utils/webhooks/responders/018cb666-e66c-755c-8d1e-7ff6cacb8641/clear 15 | Authorization: {{api-credentials}} 16 | Accept: application/json 17 | 18 | ### Get all responders. 19 | GET {{host}}/api/utils/webhooks/responders 20 | Cookie: {{cookie-credentials}} 21 | Accept: application/json 22 | 23 | ### Get all responders stats. 24 | GET {{host}}/api/utils/webhooks/responders/stats 25 | Cookie: {{cookie-credentials}} 26 | Accept: application/json 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /dev/docker/postgres_init.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA kratos; 2 | -------------------------------------------------------------------------------- /dev/docker/user_identity.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "user_identity.schema.json", 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "title": "User", 5 | "type": "object", 6 | "properties": { 7 | "traits": { 8 | "type": "object", 9 | "properties": { 10 | "email": { 11 | "type": "string", 12 | "format": "email", 13 | "title": "E-Mail", 14 | "minLength": 3, 15 | "ory.sh/kratos": { 16 | "credentials": { 17 | "password": { 18 | "identifier": true 19 | }, 20 | "webauthn": { 21 | "identifier": true 22 | } 23 | }, 24 | "verification": { 25 | "via": "email" 26 | }, 27 | "recovery": { 28 | "via": "email" 29 | } 30 | } 31 | } 32 | }, 33 | "required": [ 34 | "email" 35 | ], 36 | "additionalProperties": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /migrations/20240625210528_v1.0.0-beta.2.sql: -------------------------------------------------------------------------------- 1 | -- Rename responder's path column to location. 2 | ALTER TABLE user_data_webhooks_responders RENAME COLUMN path TO location; 3 | 4 | -- Migrate all responders to use root subdomain (@) and exact path match (=). 5 | UPDATE user_data_webhooks_responders SET location = CONCAT(':=:', location); 6 | -------------------------------------------------------------------------------- /migrations/sqlite-legacy/20230614183626_v1.0.0-alpha.2.sql: -------------------------------------------------------------------------------- 1 | -- Change "Web Scrapping" to "Web Scraping". 2 | UPDATE utils 3 | SET name = 'Web Scraping', 4 | handle = 'web_scraping' 5 | WHERE 6 | id = 9; 7 | 8 | -- Change "Resources scrapper" to "Resources trackers". 9 | UPDATE utils 10 | SET name = 'Resources trackers', 11 | keywords = 'web scraping crawl spider scraper scrape resources tracker track javascript css', 12 | handle = 'web_scraping__resources' 13 | WHERE 14 | id = 10 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commitlint": { 3 | "extends": [ 4 | "@commitlint/config-conventional" 5 | ] 6 | }, 7 | "workspaces": [ 8 | "components/secutils-docs", 9 | "components/secutils-webui" 10 | ], 11 | "devDependencies": { 12 | "@commitlint/cli": "^19.8.0", 13 | "@commitlint/config-conventional": "^19.8.0", 14 | "husky": "^9.1.7" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | imports_granularity = "Crate" 3 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Config, 3 | database::Database, 4 | network::{DnsResolver, EmailTransport, Network}, 5 | search::SearchIndex, 6 | }; 7 | use handlebars::Handlebars; 8 | 9 | pub struct Api { 10 | pub db: Database, 11 | pub search_index: SearchIndex, 12 | pub config: Config, 13 | pub network: Network, 14 | pub templates: Handlebars<'static>, 15 | } 16 | 17 | impl Api { 18 | /// Instantiates APIs collection with the specified config and datastore. 19 | pub fn new( 20 | config: Config, 21 | database: Database, 22 | search_index: SearchIndex, 23 | network: Network, 24 | templates: Handlebars<'static>, 25 | ) -> Self { 26 | Self { 27 | config, 28 | db: database, 29 | search_index, 30 | network, 31 | templates, 32 | } 33 | } 34 | } 35 | 36 | impl AsRef> for Api { 37 | fn as_ref(&self) -> &Self { 38 | self 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/config/subscriptions_config/subscription_config.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{ 2 | SubscriptionCertificatesConfig, SubscriptionWebScrapingConfig, SubscriptionWebSecurityConfig, 3 | SubscriptionWebhooksConfig, 4 | }; 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)] 8 | pub struct SubscriptionConfig { 9 | /// The config managing the webhooks utilities for a particular subscription. 10 | pub webhooks: SubscriptionWebhooksConfig, 11 | /// The config managing the web scraping utilities for a particular subscription. 12 | pub web_scraping: SubscriptionWebScrapingConfig, 13 | /// The config managing the certificates utilities for a particular subscription. 14 | pub certificates: SubscriptionCertificatesConfig, 15 | /// The config managing the web security utilities for a particular subscription. 16 | pub web_security: SubscriptionWebSecurityConfig, 17 | } 18 | -------------------------------------------------------------------------------- /src/config/utils_config.rs: -------------------------------------------------------------------------------- 1 | use crate::server::WebhookUrlType; 2 | use serde_derive::{Deserialize, Serialize}; 3 | 4 | /// Configuration for the JS runtime (Deno). 5 | #[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] 6 | pub struct UtilsConfig { 7 | /// Describes the preferred way to construct webhook URLs. 8 | pub webhook_url_type: WebhookUrlType, 9 | } 10 | 11 | impl Default for UtilsConfig { 12 | fn default() -> Self { 13 | Self { 14 | webhook_url_type: WebhookUrlType::Subdomain, 15 | } 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use crate::{config::UtilsConfig, server::WebhookUrlType}; 22 | use insta::assert_toml_snapshot; 23 | 24 | #[test] 25 | fn serialization_and_default() { 26 | assert_toml_snapshot!(UtilsConfig::default(), @"webhook_url_type = 'subdomain'"); 27 | } 28 | 29 | #[test] 30 | fn deserialization() { 31 | let config: UtilsConfig = toml::from_str(r#"webhook_url_type = 'path'"#).unwrap(); 32 | assert_eq!( 33 | config, 34 | UtilsConfig { 35 | webhook_url_type: WebhookUrlType::Path 36 | } 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use sqlx::{PgPool, Pool, Postgres}; 3 | 4 | #[derive(Clone)] 5 | pub struct Database { 6 | pub(crate) pool: Pool, 7 | } 8 | 9 | /// Common methods for the primary database, extensions are implemented separately in every module. 10 | impl Database { 11 | /// Opens database "connection". 12 | pub async fn create(pool: PgPool) -> anyhow::Result { 13 | sqlx::migrate!("./migrations") 14 | .run(&pool) 15 | .await 16 | .with_context(|| "Failed to migrate database")?; 17 | 18 | Ok(Database { pool }) 19 | } 20 | } 21 | 22 | impl AsRef for Database { 23 | fn as_ref(&self) -> &Self { 24 | self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/directories.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, anyhow}; 2 | use directories::ProjectDirs; 3 | use std::{ 4 | fs, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | pub struct Directories; 9 | impl Directories { 10 | pub fn ensure_data_dir_exists() -> anyhow::Result { 11 | ProjectDirs::from("dev", "secutils.dev", "secutils") 12 | .ok_or_else(|| anyhow!("Project data directory is not available.")) 13 | .and_then(|project_dirs| { 14 | let data_dir = project_dirs.data_dir(); 15 | 16 | Self::ensure_dir_exists(data_dir)?; 17 | 18 | Ok(data_dir.to_path_buf()) 19 | }) 20 | } 21 | 22 | pub fn ensure_dir_exists>(absolute_path: P) -> anyhow::Result<()> { 23 | fs::create_dir_all(absolute_path.as_ref()) 24 | .map_err(|err| { 25 | log::error!("Cannot create {:?} dir: {:?}", absolute_path.as_ref(), err); 26 | err 27 | }) 28 | .with_context(|| format!("Cannot create {:?} dir.", absolute_path.as_ref())) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/error/error_kind.rs: -------------------------------------------------------------------------------- 1 | /// Describes a Secutils.dev specific error types. 2 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 3 | pub enum ErrorKind { 4 | /// Error caused by the error on the client side. 5 | ClientError, 6 | /// Error caused by the lack of privileges to perform an action. 7 | AccessForbidden, 8 | /// Unknown error. 9 | Unknown, 10 | } 11 | -------------------------------------------------------------------------------- /src/js_runtime/js_runtime_config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// Configuration for the JS runtime (Deno). 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | pub struct JsRuntimeConfig { 6 | /// The hard limit for the JS runtime heap size in bytes. 7 | pub max_heap_size: usize, 8 | /// The maximum duration for a single JS script execution. 9 | pub max_user_script_execution_time: Duration, 10 | } 11 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | mod job_log_context; 2 | mod metrics_context; 3 | mod user_log_context; 4 | mod utils_resource_log_context; 5 | 6 | pub use self::{ 7 | job_log_context::JobLogContext, metrics_context::MetricsContext, 8 | user_log_context::UserLogContext, utils_resource_log_context::UtilsResourceLogContext, 9 | }; 10 | -------------------------------------------------------------------------------- /src/logging/job_log_context.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use uuid::Uuid; 3 | 4 | /// Represents a context for the job used for the structured logging. 5 | #[derive(Serialize, Debug, Copy, Clone, PartialEq)] 6 | pub struct JobLogContext { 7 | /// Unique id of the job. 8 | pub id: Uuid, 9 | } 10 | 11 | impl JobLogContext { 12 | /// Returns context used for the structured logging. 13 | pub fn new(id: Uuid) -> Self { 14 | Self { id } 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use crate::logging::JobLogContext; 21 | use insta::assert_json_snapshot; 22 | use uuid::uuid; 23 | 24 | #[test] 25 | fn serialization() -> anyhow::Result<()> { 26 | assert_json_snapshot!(JobLogContext::new(uuid!("00000000-0000-0000-0000-000000000001")), @r###" 27 | { 28 | "id": "00000000-0000-0000-0000-000000000001" 29 | } 30 | "###); 31 | 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/network/email_transport.rs: -------------------------------------------------------------------------------- 1 | use lettre::{ 2 | AsyncSmtpTransport, AsyncTransport, Tokio1Executor, 3 | transport::{ 4 | smtp::Error as SmtpError, 5 | stub::{AsyncStubTransport, Error as StubError}, 6 | }, 7 | }; 8 | use std::error::Error as StdError; 9 | 10 | pub trait EmailTransport: AsyncTransport + Sync + Send + 'static {} 11 | impl EmailTransport for AsyncSmtpTransport {} 12 | impl EmailTransport for AsyncStubTransport {} 13 | 14 | pub trait EmailTransportError: StdError + Sync + Send {} 15 | impl EmailTransportError for SmtpError {} 16 | impl EmailTransportError for StubError {} 17 | -------------------------------------------------------------------------------- /src/notifications.rs: -------------------------------------------------------------------------------- 1 | mod api_ext; 2 | mod database_ext; 3 | mod email; 4 | mod notification; 5 | mod notification_content; 6 | mod notification_content_template; 7 | mod notification_destination; 8 | mod notification_id; 9 | 10 | pub use self::{ 11 | email::{ 12 | EmailNotificationAttachment, EmailNotificationAttachmentDisposition, 13 | EmailNotificationContent, 14 | }, 15 | notification::Notification, 16 | notification_content::NotificationContent, 17 | notification_content_template::NotificationContentTemplate, 18 | notification_destination::NotificationDestination, 19 | notification_id::NotificationId, 20 | }; 21 | -------------------------------------------------------------------------------- /src/notifications/email.rs: -------------------------------------------------------------------------------- 1 | mod email_notification_attachment; 2 | mod email_notification_attachment_disposition; 3 | mod email_notification_content; 4 | 5 | pub use self::{ 6 | email_notification_attachment::EmailNotificationAttachment, 7 | email_notification_attachment_disposition::EmailNotificationAttachmentDisposition, 8 | email_notification_content::EmailNotificationContent, 9 | }; 10 | -------------------------------------------------------------------------------- /src/notifications/email/email_notification_attachment_disposition.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Describes the disposition of a email notification content attachment with an arbitrary ID. 4 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 5 | pub enum EmailNotificationAttachmentDisposition { 6 | /// Notification email attachment should be inlined. 7 | Inline(String), 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use super::EmailNotificationAttachmentDisposition; 13 | 14 | #[test] 15 | fn serialization() -> anyhow::Result<()> { 16 | assert_eq!( 17 | postcard::to_stdvec(&EmailNotificationAttachmentDisposition::Inline( 18 | "abc".to_string() 19 | ))?, 20 | vec![0, 3, 97, 98, 99] 21 | ); 22 | 23 | Ok(()) 24 | } 25 | 26 | #[test] 27 | fn deserialization() -> anyhow::Result<()> { 28 | assert_eq!( 29 | postcard::from_bytes::(&[0, 3, 97, 98, 99])?, 30 | EmailNotificationAttachmentDisposition::Inline("abc".to_string()) 31 | ); 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scheduler/database_ext/raw_scheduler_job_stored_data.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(Debug, Eq, PartialEq, Clone)] 4 | pub struct RawSchedulerJobStoredData { 5 | pub id: Uuid, 6 | pub last_updated: Option, 7 | pub last_tick: Option, 8 | pub next_tick: Option, 9 | pub job_type: i32, 10 | pub count: Option, 11 | pub ran: Option, 12 | pub stopped: Option, 13 | pub schedule: Option, 14 | pub repeating: Option, 15 | pub repeated_every: Option, 16 | pub extra: Option>, 17 | pub time_offset_seconds: Option, 18 | } 19 | -------------------------------------------------------------------------------- /src/scheduler/scheduler_job_config.rs: -------------------------------------------------------------------------------- 1 | use crate::scheduler::SchedulerJobRetryStrategy; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Represents a job configuration that can be scheduled. 5 | #[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct SchedulerJobConfig { 8 | /// Defines a schedule for the job. 9 | pub schedule: String, 10 | /// Defines a retry strategy for the job. 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub retry_strategy: Option, 13 | /// Indicates whether the job result should result into a notification. If retry strategy is 14 | /// defined, the error notification will be sent only if the job fails after all the retries. 15 | pub notifications: bool, 16 | } 17 | -------------------------------------------------------------------------------- /src/scheduler/scheduler_job_retry_state.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use time::OffsetDateTime; 3 | 4 | /// Describes the state of a job that is being retried. 5 | #[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialEq, Eq)] 6 | pub struct SchedulerJobRetryState { 7 | /// How many times the job has been retried. 8 | pub attempts: u32, 9 | /// The time at which the job will be retried. 10 | pub next_at: OffsetDateTime, 11 | } 12 | -------------------------------------------------------------------------------- /src/scheduler/scheduler_jobs.rs: -------------------------------------------------------------------------------- 1 | mod notifications_send_job; 2 | mod web_page_trackers_fetch_job; 3 | mod web_page_trackers_schedule_job; 4 | mod web_page_trackers_trigger_job; 5 | 6 | pub(crate) use notifications_send_job::NotificationsSendJob; 7 | pub(crate) use web_page_trackers_fetch_job::WebPageTrackersFetchJob; 8 | pub(crate) use web_page_trackers_schedule_job::WebPageTrackersScheduleJob; 9 | pub(crate) use web_page_trackers_trigger_job::WebPageTrackersTriggerJob; 10 | -------------------------------------------------------------------------------- /src/search.rs: -------------------------------------------------------------------------------- 1 | mod api_ext; 2 | mod search_filter; 3 | mod search_index; 4 | mod search_index_initializer; 5 | mod search_index_schema_fields; 6 | mod search_item; 7 | 8 | pub use self::{ 9 | search_filter::SearchFilter, search_index::SearchIndex, 10 | search_index_initializer::populate_search_index, search_item::SearchItem, 11 | }; 12 | -------------------------------------------------------------------------------- /src/search/search_filter.rs: -------------------------------------------------------------------------------- 1 | use crate::users::UserId; 2 | 3 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 4 | pub struct SearchFilter<'q, 'c> { 5 | pub user_id: Option, 6 | pub query: Option<&'q str>, 7 | pub category: Option<&'c str>, 8 | } 9 | 10 | impl<'q, 'c> SearchFilter<'q, 'c> { 11 | pub fn with_user_id(self, user_id: UserId) -> Self { 12 | Self { 13 | user_id: Some(user_id), 14 | ..self 15 | } 16 | } 17 | 18 | pub fn with_query(self, query: &'q str) -> Self { 19 | Self { 20 | query: Some(query), 21 | ..self 22 | } 23 | } 24 | 25 | pub fn with_category(self, category: &'c str) -> Self { 26 | Self { 27 | category: Some(category), 28 | ..self 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/security.rs: -------------------------------------------------------------------------------- 1 | mod api_ext; 2 | mod credentials; 3 | mod jwt; 4 | pub mod kratos; 5 | mod operator; 6 | 7 | pub use self::{api_ext::USER_HANDLE_LENGTH_BYTES, credentials::Credentials, operator::Operator}; 8 | -------------------------------------------------------------------------------- /src/security/credentials.rs: -------------------------------------------------------------------------------- 1 | use actix_web::cookie::Cookie; 2 | 3 | /// Represents user credentials. 4 | #[derive(Debug, Clone)] 5 | pub enum Credentials { 6 | /// Kratos session cookie. 7 | SessionCookie(Cookie<'static>), 8 | /// JSON Web Token tied to a Kratos identity. 9 | Jwt(String), 10 | } 11 | -------------------------------------------------------------------------------- /src/security/jwt.rs: -------------------------------------------------------------------------------- 1 | mod claims; 2 | 3 | pub use claims::Claims; 4 | -------------------------------------------------------------------------------- /src/security/jwt/claims.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde_with::{TimestampSeconds, serde_as}; 3 | use time::OffsetDateTime; 4 | 5 | /// JWT claims struct. 6 | #[serde_as] 7 | #[derive(Debug, Deserialize, Eq, PartialEq)] 8 | pub struct Claims { 9 | /// User email. 10 | pub sub: String, 11 | /// Token expiration time (UTC timestamp). 12 | #[serde_as(as = "TimestampSeconds")] 13 | pub exp: OffsetDateTime, 14 | } 15 | 16 | #[cfg(test)] 17 | mod test { 18 | use crate::security::jwt::Claims; 19 | use time::OffsetDateTime; 20 | 21 | #[test] 22 | fn deserialization() -> anyhow::Result<()> { 23 | assert_eq!( 24 | serde_json::from_str::( 25 | r#" 26 | { 27 | "sub": "dev@secutils.dev", 28 | "exp": 1262340000 29 | }"# 30 | )?, 31 | Claims { 32 | sub: "dev@secutils.dev".to_string(), 33 | exp: OffsetDateTime::from_unix_timestamp(1262340000)?, 34 | } 35 | ); 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/security/kratos.rs: -------------------------------------------------------------------------------- 1 | mod email_template_type; 2 | mod identity; 3 | mod identity_traits; 4 | mod identity_verifiable_address; 5 | mod session; 6 | 7 | pub use self::{ 8 | email_template_type::EmailTemplateType, identity::Identity, identity_traits::IdentityTraits, 9 | identity_verifiable_address::IdentityVerifiableAddress, session::Session, 10 | }; 11 | -------------------------------------------------------------------------------- /src/security/kratos/identity_traits.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | 3 | /// Traits represent an identity's traits. The identity is able to create, modify, and delete traits 4 | /// in a self-service manner. The input will always be validated against the JSON Schema. 5 | #[derive(Debug, PartialEq, Deserialize)] 6 | pub struct IdentityTraits { 7 | /// Main user email address. 8 | pub email: String, 9 | } 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use crate::security::kratos::IdentityTraits; 14 | 15 | #[test] 16 | fn deserialization() -> anyhow::Result<()> { 17 | assert_eq!( 18 | serde_json::from_str::( 19 | r#" 20 | { 21 | "email": "dev@secutils.dev" 22 | } 23 | "# 24 | )?, 25 | IdentityTraits { 26 | email: "dev@secutils.dev".to_string() 27 | } 28 | ); 29 | 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/security/kratos/identity_verifiable_address.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | 3 | /// The address (email or SMS) that can be verified by the user. 4 | #[derive(Debug, PartialEq, Deserialize)] 5 | pub struct IdentityVerifiableAddress { 6 | /// The address value. 7 | pub value: String, 8 | /// Indicates if the address has already been verified 9 | pub verified: bool, 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use crate::security::kratos::IdentityVerifiableAddress; 15 | 16 | #[test] 17 | fn deserialization() -> anyhow::Result<()> { 18 | assert_eq!( 19 | serde_json::from_str::( 20 | r#" 21 | { 22 | "value": "dev@secutils.dev", 23 | "verified": true 24 | } 25 | "# 26 | )?, 27 | IdentityVerifiableAddress { 28 | value: "dev@secutils.dev".to_string(), 29 | verified: true, 30 | } 31 | ); 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/security/operator.rs: -------------------------------------------------------------------------------- 1 | /// Struct to represent an operator account. 2 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 3 | pub struct Operator(String); 4 | impl Operator { 5 | /// Creates a new operator account with the provided ID. 6 | pub fn new(id: impl Into) -> Self { 7 | Self(id.into()) 8 | } 9 | 10 | /// Returns the ID of the operator account. 11 | pub fn id(&self) -> &str { 12 | &self.0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/server/extractors.rs: -------------------------------------------------------------------------------- 1 | mod credentials; 2 | mod operator; 3 | mod user; 4 | mod user_share; 5 | -------------------------------------------------------------------------------- /src/server/extractors/credentials.rs: -------------------------------------------------------------------------------- 1 | use crate::{security::Credentials, server::app_state::AppState}; 2 | use actix_web::{Error, FromRequest, HttpRequest, dev::Payload, error::ErrorUnauthorized, web}; 3 | use actix_web_httpauth::extractors::bearer::BearerAuth; 4 | use anyhow::anyhow; 5 | use std::{future::Future, pin::Pin}; 6 | 7 | impl FromRequest for Credentials { 8 | type Error = Error; 9 | type Future = Pin>>>; 10 | 11 | fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 12 | let req = req.clone(); 13 | Box::pin(async move { 14 | let state = web::Data::::extract(&req).await?; 15 | Ok(match Option::::extract(&req).await? { 16 | Some(bearer_auth) => Credentials::Jwt(bearer_auth.token().to_string()), 17 | None => Credentials::SessionCookie( 18 | req.cookie(&state.config.security.session_cookie_name) 19 | .ok_or_else(|| ErrorUnauthorized(anyhow!("Unauthorized")))?, 20 | ), 21 | }) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/server/extractors/user.rs: -------------------------------------------------------------------------------- 1 | use crate::{security::Credentials, server::app_state::AppState, users::User}; 2 | use actix_web::{ 3 | Error, FromRequest, HttpRequest, 4 | dev::Payload, 5 | error::{ErrorInternalServerError, ErrorUnauthorized}, 6 | web, 7 | }; 8 | use anyhow::anyhow; 9 | use std::{future::Future, pin::Pin}; 10 | 11 | impl FromRequest for User { 12 | type Error = Error; 13 | type Future = Pin>>>; 14 | 15 | fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 16 | let req = req.clone(); 17 | Box::pin(async move { 18 | let state = web::Data::::extract(&req).await?; 19 | let credentials = Credentials::extract(&req).await?; 20 | match state.api.security().authenticate(credentials).await { 21 | Ok(Some(user)) => Ok(user), 22 | Ok(None) => Err(ErrorUnauthorized(anyhow!("Unauthorized"))), 23 | Err(err) => { 24 | log::error!("Failed to extract user information due to: {err:?}"); 25 | Err(ErrorInternalServerError(anyhow!("Internal server error"))) 26 | } 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/server/handlers.rs: -------------------------------------------------------------------------------- 1 | mod scheduler_parse_schedule; 2 | mod search; 3 | mod security_subscription_update; 4 | mod security_users_email; 5 | mod security_users_get; 6 | mod security_users_get_by_email; 7 | mod security_users_get_self; 8 | mod security_users_remove; 9 | mod security_users_signup; 10 | mod send_message; 11 | mod status_get; 12 | mod status_set; 13 | mod ui_state_get; 14 | mod user_data_get; 15 | mod user_data_set; 16 | mod utils_action; 17 | mod webhooks_responders; 18 | 19 | pub use self::{ 20 | scheduler_parse_schedule::scheduler_parse_schedule, search::search, 21 | security_subscription_update::security_subscription_update, 22 | security_users_email::security_users_email, security_users_get::security_users_get, 23 | security_users_get_by_email::security_users_get_by_email, 24 | security_users_get_self::security_users_get_self, security_users_remove::security_users_remove, 25 | security_users_signup::security_users_signup, send_message::send_message, 26 | status_get::status_get, status_set::status_set, ui_state_get::ui_state_get, 27 | user_data_get::user_data_get, user_data_set::user_data_set, utils_action::utils_action, 28 | webhooks_responders::webhooks_responders, 29 | }; 30 | -------------------------------------------------------------------------------- /src/server/handlers/search.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | search::SearchFilter, 3 | server::{app_state::AppState, http_errors::generic_internal_server_error}, 4 | users::User, 5 | }; 6 | use actix_web::{HttpResponse, Responder, web}; 7 | use serde::Deserialize; 8 | 9 | #[derive(Deserialize)] 10 | pub struct SearchParams { 11 | pub query: String, 12 | } 13 | 14 | pub async fn search( 15 | state: web::Data, 16 | user: User, 17 | body_params: web::Json, 18 | ) -> impl Responder { 19 | let search_filter = SearchFilter::default() 20 | .with_query(&body_params.query) 21 | .with_user_id(user.id); 22 | match state.api.search().search(search_filter) { 23 | Ok(search_items) => HttpResponse::Ok().json(search_items), 24 | Err(err) => { 25 | log::error!("Failed to perform search: {:?}", err); 26 | generic_internal_server_error() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/server/handlers/security_users_get.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | logging::UserLogContext, 3 | security::Operator, 4 | server::{AppState, http_errors::generic_internal_server_error}, 5 | users::UserId, 6 | }; 7 | use actix_web::{Error, HttpResponse, Responder, web}; 8 | 9 | pub async fn security_users_get( 10 | state: web::Data, 11 | operator: Operator, 12 | user_id: web::Path, 13 | ) -> impl Responder { 14 | Ok::(match state.api.users().get(*user_id).await { 15 | Ok(Some(user_to_retrieve)) => HttpResponse::Ok().json(user_to_retrieve), 16 | Ok(None) => HttpResponse::NotFound().finish(), 17 | Err(err) => { 18 | log::error!( 19 | operator:serde = operator.id(), 20 | user:serde = UserLogContext::new(*user_id); 21 | "Failed to retrieve user by ID: {err:?}" 22 | ); 23 | generic_internal_server_error() 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/server/handlers/security_users_get_by_email.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | security::Operator, 3 | server::{AppState, http_errors::generic_internal_server_error}, 4 | }; 5 | use actix_web::{Error, HttpResponse, Responder, web}; 6 | use serde::Deserialize; 7 | 8 | #[derive(Deserialize)] 9 | pub struct Query { 10 | email: String, 11 | } 12 | 13 | pub async fn security_users_get_by_email( 14 | state: web::Data, 15 | operator: Operator, 16 | query: web::Query, 17 | ) -> impl Responder { 18 | Ok::(match state.api.users().get_by_email(&query.email).await { 19 | Ok(Some(user_to_retrieve)) => HttpResponse::Ok().json(user_to_retrieve), 20 | Ok(None) => HttpResponse::NotFound().finish(), 21 | Err(err) => { 22 | log::error!(operator:serde = operator.id(); "Failed to retrieve user by email: {err:?}"); 23 | generic_internal_server_error() 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/server/handlers/security_users_get_self.rs: -------------------------------------------------------------------------------- 1 | use crate::users::User; 2 | use actix_web::{HttpResponse, Responder}; 3 | 4 | pub async fn security_users_get_self(user: User) -> impl Responder { 5 | HttpResponse::Ok().json(user) 6 | } 7 | -------------------------------------------------------------------------------- /src/server/handlers/status_get.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Error as SecutilsError, server::app_state::AppState}; 2 | use actix_web::{HttpResponse, web}; 3 | use anyhow::anyhow; 4 | use std::ops::Deref; 5 | 6 | pub async fn status_get(state: web::Data) -> Result { 7 | state 8 | .status 9 | .read() 10 | .map(|status| HttpResponse::Ok().json(status.deref())) 11 | .map_err(|err| { 12 | log::error!("Failed to read status: {err}"); 13 | SecutilsError::from(anyhow!("Status is not available.")) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/server/handlers/status_set.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | security::Operator, 3 | server::{StatusLevel, app_state::AppState}, 4 | }; 5 | use actix_web::{HttpResponse, Responder, error::ErrorInternalServerError, web}; 6 | use anyhow::anyhow; 7 | use serde::Deserialize; 8 | 9 | #[derive(Deserialize)] 10 | pub struct SetStatusAPIParams { 11 | pub level: StatusLevel, 12 | } 13 | 14 | pub async fn status_set( 15 | state: web::Data, 16 | body_params: web::Json, 17 | operator: Operator, 18 | ) -> impl Responder { 19 | state 20 | .status 21 | .write() 22 | .map(|mut status| { 23 | status.level = body_params.level; 24 | HttpResponse::NoContent().finish() 25 | }) 26 | .map_err(|err| { 27 | log::error!(operator:serde = operator.id(); "Failed to set server status: {err:?}."); 28 | ErrorInternalServerError(anyhow!("Failed to set server status: {:?}.", err)) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/server/http_errors.rs: -------------------------------------------------------------------------------- 1 | use actix_web::HttpResponse; 2 | use serde_json::json; 3 | 4 | pub fn generic_internal_server_error() -> HttpResponse { 5 | HttpResponse::InternalServerError() 6 | .json(json!({ "message": "The operation could not be completed due to a system error. Please try again later or contact us for assistance." })) 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use crate::server::http_errors::generic_internal_server_error; 12 | 13 | #[test] 14 | fn creates_generic_internal_server_error() -> anyhow::Result<()> { 15 | let response = generic_internal_server_error(); 16 | assert_eq!(response.status().as_u16(), 500); 17 | 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/server/ui_state/status.rs: -------------------------------------------------------------------------------- 1 | use crate::server::StatusLevel; 2 | use serde::Serialize; 3 | 4 | #[derive(Clone, Serialize)] 5 | pub struct Status { 6 | pub version: String, 7 | pub level: StatusLevel, 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use crate::server::{Status, StatusLevel}; 13 | use insta::assert_json_snapshot; 14 | 15 | #[test] 16 | fn serialization() -> anyhow::Result<()> { 17 | assert_json_snapshot!(Status { 18 | version: "1.0.0-alpha.4".to_string(), 19 | level: StatusLevel::Available, 20 | }, @r###" 21 | { 22 | "version": "1.0.0-alpha.4", 23 | "level": "available" 24 | } 25 | "###); 26 | 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/server/ui_state/status_level.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialOrd, PartialEq)] 4 | #[serde(rename_all = "camelCase")] 5 | pub enum StatusLevel { 6 | Available, 7 | Unavailable, 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use crate::server::StatusLevel; 13 | 14 | #[test] 15 | fn serialization() -> anyhow::Result<()> { 16 | assert_eq!( 17 | serde_json::to_string(&StatusLevel::Available)?, 18 | r#""available""# 19 | ); 20 | assert_eq!( 21 | serde_json::to_string(&StatusLevel::Unavailable)?, 22 | r#""unavailable""# 23 | ); 24 | 25 | Ok(()) 26 | } 27 | 28 | #[test] 29 | fn deserialization() -> anyhow::Result<()> { 30 | assert_eq!( 31 | serde_json::from_str::(r#""available""#)?, 32 | StatusLevel::Available 33 | ); 34 | assert_eq!( 35 | serde_json::from_str::(r#""unavailable""#)?, 36 | StatusLevel::Unavailable 37 | ); 38 | 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/templates.rs: -------------------------------------------------------------------------------- 1 | use handlebars::Handlebars; 2 | use rust_embed::RustEmbed; 3 | 4 | #[derive(RustEmbed)] 5 | #[folder = "assets/templates"] 6 | #[include = "*.hbs"] 7 | struct TemplateAssets; 8 | 9 | /// Creates a handlebars instance with embedded templates. 10 | pub fn create_templates<'reg>() -> anyhow::Result> { 11 | let mut handlebars = Handlebars::new(); 12 | handlebars.register_embed_templates_with_extension::(".hbs")?; 13 | Ok(handlebars) 14 | } 15 | -------------------------------------------------------------------------------- /src/users.rs: -------------------------------------------------------------------------------- 1 | pub mod api_ext; 2 | mod database_ext; 3 | mod user; 4 | mod user_data; 5 | mod user_data_key; 6 | mod user_data_namespace; 7 | mod user_id; 8 | mod user_settings; 9 | mod user_share; 10 | mod user_subscription; 11 | 12 | pub use self::{ 13 | api_ext::errors::UserSignupError, 14 | user::User, 15 | user_data::UserData, 16 | user_data_key::UserDataKey, 17 | user_data_namespace::UserDataNamespace, 18 | user_id::UserId, 19 | user_settings::{UserSettings, UserSettingsSetter}, 20 | user_share::{ClientUserShare, SharedResource, UserShare, UserShareId}, 21 | user_subscription::{ 22 | ClientSubscriptionFeatures, SubscriptionFeatures, SubscriptionTier, UserSubscription, 23 | }, 24 | }; 25 | 26 | pub(crate) use self::api_ext::user_data_setters::DictionaryDataUserDataSetter; 27 | -------------------------------------------------------------------------------- /src/users/api_ext/errors.rs: -------------------------------------------------------------------------------- 1 | mod user_signup_error; 2 | 3 | pub use user_signup_error::UserSignupError; 4 | -------------------------------------------------------------------------------- /src/users/api_ext/errors/user_signup_error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{Debug, Display, Formatter}, 4 | }; 5 | 6 | /// Represents possible errors that can happen during signup. 7 | #[derive(Debug)] 8 | pub enum UserSignupError { 9 | EmailAlreadyRegistered, 10 | } 11 | 12 | impl Display for UserSignupError { 13 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 14 | Debug::fmt(self, f) 15 | } 16 | } 17 | 18 | impl Error for UserSignupError {} 19 | -------------------------------------------------------------------------------- /src/users/api_ext/user_data_setters.rs: -------------------------------------------------------------------------------- 1 | mod dictionary_data_user_data_setter; 2 | 3 | pub(crate) use dictionary_data_user_data_setter::DictionaryDataUserDataSetter; 4 | -------------------------------------------------------------------------------- /src/users/user_data.rs: -------------------------------------------------------------------------------- 1 | use crate::users::UserId; 2 | use time::OffsetDateTime; 3 | 4 | #[derive(Debug, Eq, PartialEq, Clone)] 5 | pub struct UserData { 6 | pub user_id: UserId, 7 | pub key: Option, 8 | pub value: V, 9 | pub timestamp: OffsetDateTime, 10 | } 11 | 12 | impl UserData { 13 | pub fn new(user_id: UserId, value: V, timestamp: OffsetDateTime) -> Self { 14 | Self { 15 | user_id, 16 | key: None, 17 | value, 18 | timestamp, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | mod api_ext; 2 | pub mod certificates; 3 | mod database_ext; 4 | mod user_share_ext; 5 | mod util; 6 | mod utils_action; 7 | mod utils_action_params; 8 | mod utils_action_result; 9 | mod utils_action_validation; 10 | mod utils_resource; 11 | mod utils_resource_operation; 12 | pub mod web_scraping; 13 | pub mod web_security; 14 | pub mod webhooks; 15 | 16 | pub use self::{ 17 | util::Util, utils_action::UtilsAction, utils_action_params::UtilsActionParams, 18 | utils_action_result::UtilsActionResult, utils_resource::UtilsResource, 19 | utils_resource_operation::UtilsResourceOperation, 20 | }; 21 | 22 | #[cfg(test)] 23 | pub mod tests { 24 | pub use super::{ 25 | certificates::tests::MockCertificateAttributes, 26 | web_scraping::tests::MockWebPageTrackerBuilder, webhooks::tests::MockResponderBuilder, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/certificates/certificate_templates.rs: -------------------------------------------------------------------------------- 1 | mod certificate_attributes; 2 | mod certificate_template; 3 | 4 | pub use self::{ 5 | certificate_attributes::CertificateAttributes, certificate_template::CertificateTemplate, 6 | }; 7 | 8 | #[cfg(test)] 9 | pub mod tests { 10 | pub use super::certificate_attributes::tests::MockCertificateAttributes; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/certificates/private_keys.rs: -------------------------------------------------------------------------------- 1 | mod private_key; 2 | mod private_key_algorithm; 3 | mod private_key_elliptic_curve; 4 | 5 | mod private_key_size; 6 | 7 | pub use self::{ 8 | private_key::PrivateKey, private_key_algorithm::PrivateKeyAlgorithm, 9 | private_key_elliptic_curve::PrivateKeyEllipticCurve, private_key_size::PrivateKeySize, 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/certificates/x509.rs: -------------------------------------------------------------------------------- 1 | mod extended_key_usage; 2 | mod key_usage; 3 | mod signature_algorithm; 4 | mod version; 5 | 6 | pub use self::{ 7 | extended_key_usage::ExtendedKeyUsage, key_usage::KeyUsage, 8 | signature_algorithm::SignatureAlgorithm, version::Version, 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/utils_action_validation.rs: -------------------------------------------------------------------------------- 1 | /// Defines the maximum length of an entity name (certificate template name, responder name etc.). 2 | pub const MAX_UTILS_ENTITY_NAME_LENGTH: usize = 100; 3 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_page_content.rs: -------------------------------------------------------------------------------- 1 | mod web_page_content_revisions_diff; 2 | mod web_page_content_tracker_tag; 3 | mod web_scraper_content_request; 4 | mod web_scraper_content_response; 5 | 6 | pub use self::{ 7 | web_page_content_revisions_diff::web_page_content_revisions_diff, 8 | web_page_content_tracker_tag::WebPageContentTrackerTag, 9 | web_scraper_content_request::{WebScraperContentRequest, WebScraperContentRequestScripts}, 10 | web_scraper_content_response::WebScraperContentResponse, 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_page_content/web_page_content_tracker_tag.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::web_scraping::{WebPageTrackerKind, WebPageTrackerTag}; 2 | 3 | /// Struct that represents a tag for the `WebPageTracker` that tracks the content of a web page. 4 | #[derive(Debug, Clone, PartialEq, Eq)] 5 | pub struct WebPageContentTrackerTag(()); 6 | impl WebPageTrackerTag for WebPageContentTrackerTag { 7 | const KIND: WebPageTrackerKind = WebPageTrackerKind::WebPageContent; 8 | type TrackerMeta = (); 9 | type TrackerData = String; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_page_resources/web_page_resource_diff_status.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | /// Represents a web page resource diff status. 4 | #[derive(Serialize, Debug, Clone, PartialEq, Eq)] 5 | #[serde(rename_all = "camelCase")] 6 | pub enum WebPageResourceDiffStatus { 7 | /// Indicates that the resource was added since last revision. 8 | Added, 9 | /// Indicates that the resource was removed since last revision. 10 | Removed, 11 | /// Indicates that the resource was changed since last revision. 12 | Changed, 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use crate::utils::web_scraping::WebPageResourceDiffStatus; 18 | use insta::assert_json_snapshot; 19 | 20 | #[test] 21 | fn serialization() -> anyhow::Result<()> { 22 | assert_json_snapshot!(WebPageResourceDiffStatus::Added, @r###""added""###); 23 | assert_json_snapshot!(WebPageResourceDiffStatus::Removed, @r###""removed""###); 24 | assert_json_snapshot!(WebPageResourceDiffStatus::Changed, @r###""changed""###); 25 | 26 | Ok(()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_page_resources/web_page_resources_tracker_tag.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::web_scraping::{ 2 | WebPageResourceInternal, WebPageResourcesData, WebPageTrackerKind, WebPageTrackerTag, 3 | }; 4 | 5 | /// Struct that represents a tag for the `WebPageTracker` that tracks the resources of a web page. 6 | #[derive(Debug, Clone, PartialEq, Eq)] 7 | pub struct WebPageResourcesTrackerTag(()); 8 | impl WebPageTrackerTag for WebPageResourcesTrackerTag { 9 | const KIND: WebPageTrackerKind = WebPageTrackerKind::WebPageResources; 10 | type TrackerMeta = (); 11 | type TrackerData = WebPageResourcesData; 12 | } 13 | 14 | /// Internal struct that represents a tag for the `WebPageTracker` that tracks the resources of a 15 | /// web page and that overrides `TrackerData` type with the type compatible with Postcard. 16 | pub(in crate::utils::web_scraping) struct WebPageResourcesTrackerInternalTag(()); 17 | impl WebPageTrackerTag for WebPageResourcesTrackerInternalTag { 18 | const KIND: WebPageTrackerKind = WebPageResourcesTrackerTag::KIND; 19 | type TrackerMeta = (); 20 | type TrackerData = WebPageResourcesData; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_page_tracker_tag.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::web_scraping::WebPageTrackerKind; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// Trait that defines kind and utility types of a web page tracker of the specific type. 5 | pub trait WebPageTrackerTag { 6 | const KIND: WebPageTrackerKind; 7 | type TrackerMeta: Clone + Serialize + for<'de> Deserialize<'de>; 8 | type TrackerData: Clone + Serialize + for<'de> Deserialize<'de>; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/web_scraping/web_page_trackers/web_scraper.rs: -------------------------------------------------------------------------------- 1 | mod web_scraper_error_response; 2 | 3 | pub use web_scraper_error_response::WebScraperErrorResponse; 4 | -------------------------------------------------------------------------------- /src/utils/web_security/api_ext/content_security_policies_serialize_params.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::web_security::ContentSecurityPolicySource; 2 | use serde::Deserialize; 3 | 4 | #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct ContentSecurityPoliciesSerializeParams { 7 | pub source: ContentSecurityPolicySource, 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use crate::utils::web_security::{ 13 | ContentSecurityPolicySource, api_ext::ContentSecurityPoliciesSerializeParams, 14 | }; 15 | 16 | #[test] 17 | fn deserialization() -> anyhow::Result<()> { 18 | assert_eq!( 19 | serde_json::from_str::( 20 | r#" 21 | { 22 | "source": "enforcingHeader" 23 | } 24 | "# 25 | )?, 26 | ContentSecurityPoliciesSerializeParams { 27 | source: ContentSecurityPolicySource::EnforcingHeader 28 | } 29 | ); 30 | 31 | Ok(()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/web_security/csp.rs: -------------------------------------------------------------------------------- 1 | mod content_security_policies; 2 | mod content_security_policy_source; 3 | 4 | pub use self::{ 5 | content_security_policies::{ 6 | ContentSecurityPolicy, ContentSecurityPolicyDirective, 7 | ContentSecurityPolicyRequireTrustedTypesForDirectiveValue, 8 | ContentSecurityPolicySandboxDirectiveValue, 9 | ContentSecurityPolicyTrustedTypesDirectiveValue, ContentSecurityPolicyWebrtcDirectiveValue, 10 | }, 11 | content_security_policy_source::ContentSecurityPolicySource, 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/web_security/csp/content_security_policies.rs: -------------------------------------------------------------------------------- 1 | mod content_security_policy; 2 | mod content_security_policy_directive; 3 | mod content_security_policy_require_trusted_types_for_directive_value; 4 | mod content_security_policy_sandbox_directive_value; 5 | mod content_security_policy_trusted_types_directive_value; 6 | mod content_security_policy_webrtc_directive_value; 7 | 8 | pub use self::{ 9 | content_security_policy::ContentSecurityPolicy, 10 | content_security_policy_directive::ContentSecurityPolicyDirective, 11 | content_security_policy_require_trusted_types_for_directive_value::ContentSecurityPolicyRequireTrustedTypesForDirectiveValue, 12 | content_security_policy_sandbox_directive_value::ContentSecurityPolicySandboxDirectiveValue, 13 | content_security_policy_trusted_types_directive_value::ContentSecurityPolicyTrustedTypesDirectiveValue, 14 | content_security_policy_webrtc_directive_value::ContentSecurityPolicyWebrtcDirectiveValue, 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/web_security/csp/content_security_policies/content_security_policy_require_trusted_types_for_directive_value.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// See https://www.w3.org/TR/trusted-types. 4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 5 | pub enum ContentSecurityPolicyRequireTrustedTypesForDirectiveValue { 6 | #[serde(rename = "'script'")] 7 | Script, 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use super::ContentSecurityPolicyRequireTrustedTypesForDirectiveValue; 13 | use insta::assert_json_snapshot; 14 | 15 | #[test] 16 | fn serialization() -> anyhow::Result<()> { 17 | assert_json_snapshot!( 18 | ContentSecurityPolicyRequireTrustedTypesForDirectiveValue::Script, 19 | @r###""'script'""### 20 | ); 21 | 22 | Ok(()) 23 | } 24 | 25 | #[test] 26 | fn deserialization() -> anyhow::Result<()> { 27 | assert_eq!( 28 | serde_json::from_str::( 29 | r#""'script'""# 30 | )?, 31 | ContentSecurityPolicyRequireTrustedTypesForDirectiveValue::Script 32 | ); 33 | 34 | Ok(()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/webhooks/api_ext/responders_request_create_params.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::webhooks::ResponderRequestHeaders; 2 | use std::{borrow::Cow, net::SocketAddr}; 3 | 4 | #[derive(Debug, PartialEq, Eq)] 5 | pub struct RespondersRequestCreateParams<'a> { 6 | /// An internet socket address of the client that made the request. 7 | pub client_address: Option, 8 | /// HTTP method of the request. 9 | pub method: Cow<'a, str>, 10 | /// HTTP headers of the request. 11 | pub headers: Option>, 12 | /// HTTP path of the request + query string. 13 | pub url: Cow<'a, str>, 14 | /// HTTP body of the request. 15 | pub body: Option>, 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/webhooks/responders.rs: -------------------------------------------------------------------------------- 1 | mod responder; 2 | mod responder_location; 3 | mod responder_method; 4 | mod responder_path_type; 5 | mod responder_request; 6 | mod responder_script_context; 7 | mod responder_script_result; 8 | mod responder_settings; 9 | mod responder_stats; 10 | 11 | pub use self::{ 12 | responder::Responder, 13 | responder_location::ResponderLocation, 14 | responder_method::ResponderMethod, 15 | responder_path_type::ResponderPathType, 16 | responder_request::{ResponderRequest, ResponderRequestHeaders}, 17 | responder_script_context::ResponderScriptContext, 18 | responder_script_result::ResponderScriptResult, 19 | responder_settings::ResponderSettings, 20 | responder_stats::ResponderStats, 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/webhooks/responders/responder_script_context.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::{collections::HashMap, net::SocketAddr}; 3 | 4 | /// Context available to the responder scripts through global `context` variable. 5 | #[derive(Serialize, Debug, PartialEq, Eq)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct ResponderScriptContext<'a> { 8 | /// An internet socket address of the client that made the request. 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub client_address: Option, 11 | /// HTTP method of the received request. 12 | pub method: &'a str, 13 | /// HTTP headers of the received request. 14 | pub headers: HashMap<&'a str, &'a str>, 15 | /// HTTP path of the received request. 16 | pub path: &'a str, 17 | /// Parsed query string of the received request. 18 | pub query: HashMap<&'a str, &'a str>, 19 | /// HTTP body of the received request. 20 | pub body: &'a [u8], 21 | } 22 | --------------------------------------------------------------------------------