feat(marketing): add security bug bounty policy (#29)
This commit is contained in:
@@ -260,6 +260,17 @@ pub fn render(ctx: Context) -> Element(a) {
|
||||
[html.text(g_(i18n_ctx, "Community Guidelines"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/security"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Security Bug Bounty"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
|
||||
@@ -189,10 +189,10 @@ pub fn render(ctx: Context) -> Element(a) {
|
||||
g_(i18n_ctx, "Found a security issue?"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"We appreciate responsible disclosure to security@fluxer.app. We offer Plutonium codes and Bug Hunter badges based on severity.",
|
||||
"We appreciate responsible disclosure via our Security Bug Bounty page. We offer Plutonium codes and Bug Hunter badges based on severity.",
|
||||
),
|
||||
"security@fluxer.app",
|
||||
"mailto:security@fluxer.app",
|
||||
g_(i18n_ctx, "Security Bug Bounty"),
|
||||
"/security",
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -414,6 +414,15 @@ pub fn render(ctx: Context, _req: Request) -> Element(a) {
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Community Guidelines"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/security"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Security Bug Bounty"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
|
||||
@@ -153,8 +153,8 @@ fn contribute_section(ctx: Context) -> Element(a) {
|
||||
i18n_ctx,
|
||||
"Report vulnerabilities and help us keep the platform secure.",
|
||||
),
|
||||
"security@fluxer.app",
|
||||
"mailto:security@fluxer.app",
|
||||
g_(i18n_ctx, "Security Bug Bounty"),
|
||||
"/security",
|
||||
),
|
||||
support_card.render(
|
||||
ctx,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/help_center
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/markdown_utils
|
||||
import fluxer_marketing/pages/layout
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/element
|
||||
import wisp
|
||||
|
||||
pub fn render(req: wisp.Request, ctx: Context) -> wisp.Response {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let help_data = help_center.load_help_articles(ctx.locale)
|
||||
|
||||
let markdown_element =
|
||||
markdown_utils.load_markdown_with_fallback("priv/security", ctx.locale)
|
||||
|> markdown_utils.render_markdown_to_element(ctx, help_data)
|
||||
|
||||
let content = [markdown_element]
|
||||
|
||||
layout.docs_layout(
|
||||
req,
|
||||
ctx,
|
||||
layout.article_page_meta(
|
||||
g_(i18n_ctx, "Security Bug Bounty"),
|
||||
g_(i18n_ctx, "Report security issues responsibly"),
|
||||
),
|
||||
g_(i18n_ctx, "Fluxer Security Bug Bounty"),
|
||||
content,
|
||||
)
|
||||
|> element.to_document_string_tree
|
||||
|> wisp.html_response(200)
|
||||
}
|
||||
@@ -291,7 +291,7 @@ fn is_table(text: String) -> Bool {
|
||||
[_header, separator, ..] -> {
|
||||
case
|
||||
regexp.compile(
|
||||
"^\\|?\\s*:?-+:?\\s*(?:\\|\\s*:?-+:?\\s*)+\\|?$",
|
||||
"^\\|?\\s*:?-+:?\\s*(?:\\|\\s*:?-+:?\\s*)*\\|?$",
|
||||
regex_options,
|
||||
)
|
||||
{
|
||||
|
||||
@@ -34,10 +34,12 @@ import fluxer_marketing/pages/partners_page
|
||||
import fluxer_marketing/pages/plutonium_page
|
||||
import fluxer_marketing/pages/press_page
|
||||
import fluxer_marketing/pages/privacy_page
|
||||
import fluxer_marketing/pages/security_page
|
||||
import fluxer_marketing/pages/terms_page
|
||||
import fluxer_marketing/sitemap
|
||||
import fluxer_marketing/web.{type Context, prepend_base_path}
|
||||
import gleam/http
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/option
|
||||
import gleam/string
|
||||
@@ -60,6 +62,8 @@ pub fn handle_request(req: Request, ctx: Context) -> Response {
|
||||
["sitemap.xml"] -> handle_sitemap(ctx)
|
||||
["terms"] -> terms_page.render(req, ctx)
|
||||
["privacy"] -> privacy_page.render(req, ctx)
|
||||
["security"] -> security_page.render(req, ctx)
|
||||
["security.txt"] -> handle_security_txt(ctx)
|
||||
["guidelines"] -> guidelines_page.render(req, ctx)
|
||||
["company-information"] -> company_page.render(req, ctx)
|
||||
["careers"] -> careers_page.render(req, ctx)
|
||||
@@ -256,6 +260,36 @@ fn handle_sitemap(ctx: Context) -> Response {
|
||||
|> wisp.string_body(xml)
|
||||
}
|
||||
|
||||
fn handle_security_txt(ctx: Context) -> Response {
|
||||
let security_url = ctx.base_url <> "/security"
|
||||
let expires = int.to_string(current_year() + 1) <> "-01-05T13:37:00.000Z"
|
||||
|
||||
let body_lines =
|
||||
[
|
||||
"Contact: " <> security_url,
|
||||
"Contact: mailto:security@fluxer.app",
|
||||
"Expires: " <> expires,
|
||||
"Preferred-Languages: en",
|
||||
"Policy: " <> security_url,
|
||||
]
|
||||
|> string.join("\n")
|
||||
|
||||
let body = body_lines <> "\n"
|
||||
|
||||
wisp.response(200)
|
||||
|> wisp.set_header("content-type", "text/plain; charset=utf-8")
|
||||
|> wisp.string_body(body)
|
||||
}
|
||||
|
||||
fn current_year() -> Int {
|
||||
case calendar_universal_time() {
|
||||
#(#(year, _, _), _) -> year
|
||||
}
|
||||
}
|
||||
|
||||
@external(erlang, "calendar", "universal_time")
|
||||
fn calendar_universal_time() -> #(#(Int, Int, Int), #(Int, Int, Int))
|
||||
|
||||
fn handle_locale_change(req: Request, ctx: Context) -> Response {
|
||||
case req.method {
|
||||
http.Post -> {
|
||||
|
||||
@@ -47,6 +47,7 @@ fn generate_urls(base_url: String) -> List(UrlEntry) {
|
||||
UrlEntry(base_url, "weekly", "1.0"),
|
||||
UrlEntry(base_url <> "/terms", "monthly", "0.5"),
|
||||
UrlEntry(base_url <> "/privacy", "monthly", "0.5"),
|
||||
UrlEntry(base_url <> "/security", "monthly", "0.5"),
|
||||
UrlEntry(base_url <> "/guidelines", "monthly", "0.7"),
|
||||
UrlEntry(base_url <> "/company-information", "monthly", "0.4"),
|
||||
UrlEntry(base_url <> "/careers", "weekly", "0.6"),
|
||||
|
||||
Reference in New Issue
Block a user