├── .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 |
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 |

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 |

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 |

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 |

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 |

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 |

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 |
--------------------------------------------------------------------------------