cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

commit 350a6cadbacc5d21f4b1a1cbbf2581e6718f0bf7
parent 53dd63fbc59fb6690b25168675b6fc10e62adb42
Author: cblgh <cblgh@cblgh.org>
Date:   Wed, 21 Sep 2022 16:22:40 +0200

fmt

Diffstat:
Mdefaults/defaults.go | 4++--
Mi18n/i18n.go | 366++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mrun.go | 4++--
Mserver/server.go | 78+++++++++++++++++++++++++++++++++++++++---------------------------------------
Mtypes/types.go | 52++++++++++++++++++++++++++--------------------------
Mutil/util.go | 40++++++++++++++++++++--------------------
6 files changed, 272 insertions(+), 272 deletions(-)

diff --git a/defaults/defaults.go b/defaults/defaults.go @@ -1,10 +1,10 @@ package defaults import ( - "embed" + "embed" ) -//go:embed sample-about.md +//go:embed sample-about.md var DEFAULT_ABOUT string //go:embed sample-logo.svg diff --git a/i18n/i18n.go b/i18n/i18n.go @@ -2,95 +2,96 @@ package i18n import ( "cerca/util" + "fmt" "html/template" - "strings" "log" - "fmt" + "strings" ) const toolURL = "https://github.com/cblgh/cerca/releases/tag/pwtool-v1" + var English = map[string]string{ - "About": "about", - "Login": "login", - "Logout": "logout", - "Sort": "sort", - "Enter": "enter", - "Register": "register", - - "LoggedIn": "logged in", - "NotLoggedIn": "Not logged in", - "LogIn": "log in", - "GoBack": "Go back", - - "SortPostsRecent": "recent posts", + "About": "about", + "Login": "login", + "Logout": "logout", + "Sort": "sort", + "Enter": "enter", + "Register": "register", + + "LoggedIn": "logged in", + "NotLoggedIn": "Not logged in", + "LogIn": "log in", + "GoBack": "Go back", + + "SortPostsRecent": "recent posts", "SortThreadsRecent": "most recent threads", - "ForumDescription": "This forum is for the <a href='{{ .CommunityLink }}'>{{.CommunityName}}</a> community.", - "LoginNoAccount": "Don't have an account yet? <a href='/register'>Register</a> one.", - "LoginFailure": "<b>Failed login attempt:</b> incorrect password, wrong username, or a non-existent user.", - "LoginAlreadyLoggedIn": `You are already logged in. Would you like to <a href="/logout">log out</a>?`, + "ForumDescription": "This forum is for the <a href='{{ .CommunityLink }}'>{{.CommunityName}}</a> community.", + "LoginNoAccount": "Don't have an account yet? <a href='/register'>Register</a> one.", + "LoginFailure": "<b>Failed login attempt:</b> incorrect password, wrong username, or a non-existent user.", + "LoginAlreadyLoggedIn": `You are already logged in. Would you like to <a href="/logout">log out</a>?`, - "Username": "username", - "Password": "password", - "PasswordMin": "Must be at least 9 characters long", + "Username": "username", + "Password": "password", + "PasswordMin": "Must be at least 9 characters long", "PasswordForgot": "Forgot your password?", - "Threads": "threads", - "ThreadNew": "new thread", - "ThreadThe": "the thread", - "Index": "index", - - "ThreadCreate": "Create thread", - "Title": "Title", - "Content": "Content", - "Create": "Create", - "TextareaPlaceholder": "Tabula rasa", - - "PasswordReset": "reset password", - "PasswordResetMessage": "You are logged in, log out to reset password using proof", - "PasswordResetSuccess": "Reset password—success!", - "PasswordResetSuccessMessage": "You reset your password!", - "PasswordResetSuccessLinkMessage": "Give it a try and", - - "RegisterMessage": "You already have an account (you are logged in with it).", - "RegisterLinkMessage": "Visit the", - "RegisterSuccess": "registered successfully", - - "ErrUnaccepted": "Unaccepted request", - "ErrThread404": "Thread not found", - "ErrThread404Message": "The thread does not exist (anymore?)", - "ErrGeneric404": "Page not found", - "ErrGeneric404Message": "The visited page does not exist (anymore?). Error code %d.", - - "NewThreadMessage": "Only members of this forum may create new threads", - "NewThreadLinkMessage": "If you are a member,", - "NewThreadCreateError": "Error creating thread", - "NewThreadCreateErrorMessage": "There was a database error when creating the thread, apologies.", - - "AriaPostMeta": "Post meta", - "AriaDeletePost": "Delete this post", - "AriaRespondIntoThread": "Respond into this thread", - "PromptDeleteQuestion": "Delete post for all posterity?", - "Delete": "delete", - "Post": "post", - "Author": "Author", - "Responded": "responded", - "YourAnswer": "Your answer", - - "AriaHome": "Home", - "ThreadStartNew": "Start a new thread", - - "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, - "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, - "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, - "RegisterKeypairWarning": "This keypair will only be displayed once", - - "RegisterRules": `To register, you need to either belong to the <a href="https://webring.xxiivv.com">Merveilles Webring</a> or the <a href="https://merveilles.town">Merveilles Fediverse instance</a>`, - - "RegisterVerificationCode": "Your verification code is", - "RegisterVerificationInstructionsTitle": "Verification instructions", - // TODO (2022-09-20): make verification instructions another md file to load, pass path from config - "RegisterVerificationInstructions": `<p>You can use either your mastodon profile or your webring site to verify your registration.</p> + "Threads": "threads", + "ThreadNew": "new thread", + "ThreadThe": "the thread", + "Index": "index", + + "ThreadCreate": "Create thread", + "Title": "Title", + "Content": "Content", + "Create": "Create", + "TextareaPlaceholder": "Tabula rasa", + + "PasswordReset": "reset password", + "PasswordResetMessage": "You are logged in, log out to reset password using proof", + "PasswordResetSuccess": "Reset password—success!", + "PasswordResetSuccessMessage": "You reset your password!", + "PasswordResetSuccessLinkMessage": "Give it a try and", + + "RegisterMessage": "You already have an account (you are logged in with it).", + "RegisterLinkMessage": "Visit the", + "RegisterSuccess": "registered successfully", + + "ErrUnaccepted": "Unaccepted request", + "ErrThread404": "Thread not found", + "ErrThread404Message": "The thread does not exist (anymore?)", + "ErrGeneric404": "Page not found", + "ErrGeneric404Message": "The visited page does not exist (anymore?). Error code %d.", + + "NewThreadMessage": "Only members of this forum may create new threads", + "NewThreadLinkMessage": "If you are a member,", + "NewThreadCreateError": "Error creating thread", + "NewThreadCreateErrorMessage": "There was a database error when creating the thread, apologies.", + + "AriaPostMeta": "Post meta", + "AriaDeletePost": "Delete this post", + "AriaRespondIntoThread": "Respond into this thread", + "PromptDeleteQuestion": "Delete post for all posterity?", + "Delete": "delete", + "Post": "post", + "Author": "Author", + "Responded": "responded", + "YourAnswer": "Your answer", + + "AriaHome": "Home", + "ThreadStartNew": "Start a new thread", + + "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, + "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, + "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, + "RegisterKeypairWarning": "This keypair will only be displayed once", + + "RegisterRules": `To register, you need to either belong to the <a href="https://webring.xxiivv.com">Merveilles Webring</a> or the <a href="https://merveilles.town">Merveilles Fediverse instance</a>`, + + "RegisterVerificationCode": "Your verification code is", + "RegisterVerificationInstructionsTitle": "Verification instructions", + // TODO (2022-09-20): make verification instructions another md file to load, pass path from config + "RegisterVerificationInstructions": `<p>You can use either your mastodon profile or your webring site to verify your registration.</p> <ul> <li><b>Mastodon:</b> temporarily add a new metadata item to <a href="https://merveilles.town/settings/profile">your profile</a> containing the verification code displayed above. Pass your profile as the verification link.</li> @@ -99,16 +100,16 @@ var English = map[string]string{ </ul> `, - "RegisterVerificationLink": "Verification link", - "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">{{ .CommunityName }} Code of Conduct</a>`, - "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">read it</a>`, + "RegisterVerificationLink": "Verification link", + "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">{{ .CommunityName }} Code of Conduct</a>`, + "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">read it</a>`, - "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", - "PasswordResetUsernameQuestion": "First up: what was your username?", - "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, - "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, - "ToolInstructions": `tool instructions`, - "PasswordResetToolInstructions": fmt.Sprintf(` + "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", + "PasswordResetUsernameQuestion": "First up: what was your username?", + "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, + "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, + "ToolInstructions": `tool instructions`, + "PasswordResetToolInstructions": fmt.Sprintf(` <ul> <li><a href="%s">Download the tool</a></li> <li>Run as:<br><code>pwtool --payload &lt;proof payload from above&gt; --keypair &lt;path to file with yr keypair from registration&gt;</code> @@ -117,94 +118,93 @@ var English = map[string]string{ <li>(Remember to save your password :)</li> </ul> `, toolURL), - "GeneratePayload": "generate payload", - "Proof": "proof", - "NewPassword": "new password", - "ChangePassword": "change password", - } - + "GeneratePayload": "generate payload", + "Proof": "proof", + "NewPassword": "new password", + "ChangePassword": "change password", +} var EspanolMexicano = map[string]string{ - "About": "acerca de", - "Login": "loguearse", - "Logout": "logout", - "Sort": "sort", - "Register": "register", - "Enter": "entrar", - - "LoggedIn": "logged in", - "NotLoggedIn": "Not logged in", - "LogIn": "log in", - "GoBack": "Go back", - - "SortRecentPosts": "recent posts", + "About": "acerca de", + "Login": "loguearse", + "Logout": "logout", + "Sort": "sort", + "Register": "register", + "Enter": "entrar", + + "LoggedIn": "logged in", + "NotLoggedIn": "Not logged in", + "LogIn": "log in", + "GoBack": "Go back", + + "SortRecentPosts": "recent posts", "SortRecentThreads": "most recent threads", - "ForumDescription": "Este foro es principalmente para las personas de la comunidad <a href='{{ .CommunityLink }}'>{{ .CommunityName }}</a>.", - "LoginNoAccount": "¿No tienes una cuenta? <a href='/register'>Registra</a> una. ", - "LoginFailure": "<b>Failed login attempt:</b> incorrect password, wrong username, or a non-existent user.", - "LoginAlreadyLoggedIn": `You are already logged in. Would you like to <a href="/logout">log out</a>?`, + "ForumDescription": "Este foro es principalmente para las personas de la comunidad <a href='{{ .CommunityLink }}'>{{ .CommunityName }}</a>.", + "LoginNoAccount": "¿No tienes una cuenta? <a href='/register'>Registra</a> una. ", + "LoginFailure": "<b>Failed login attempt:</b> incorrect password, wrong username, or a non-existent user.", + "LoginAlreadyLoggedIn": `You are already logged in. Would you like to <a href="/logout">log out</a>?`, - "Username": "usuarie", - "Password": "contraseña", - "PasswordMin": "Debe tener por lo menos 9 caracteres.", + "Username": "usuarie", + "Password": "contraseña", + "PasswordMin": "Debe tener por lo menos 9 caracteres.", "PasswordForgot": "Olvidaste tu contraseña?", - "Threads": "threads", - "ThreadNew": "new thread", - "ThreadThe": "the thread", - "Index": "index", - - "ThreadCreate": "Create thread", - "Title": "Title", - "Content": "Content", - "Create": "Create", - "TextareaPlaceholder": "Tabula rasa", - - "PasswordReset": "reset password", - "PasswordResetMessage": "You are logged in, log out to reset password using proof", - "PasswordResetSuccess": "Reset password—success!", - "PasswordResetSuccessMessage": "You reset your password!", - "PasswordResetSuccessLinkMessage": "Give it a try and", - - "RegisterMessage": "You already have an account (you are logged in with it).", - "RegisterLinkMessage": "Visit the", - "RegisterSuccess": "registered successfully", - - "ErrUnaccepted": "Unaccepted request", - "ErrThread404": "Thread not found", - "ErrThread404Message": "The thread does not exist (anymore?)", - "ErrGeneric404": "Page not found", - "ErrGeneric404Message": "The visited page does not exist (anymore?). Error code %d.", - - "NewThreadMessage": "Only members of this forum may create new threads", - "NewThreadLinkMessage": "If you are a member,", - "NewThreadCreateError": "Error creating thread", - "NewThreadCreateErrorMessage": "There was a database error when creating the thread, apologies.", - "ThreadStartNew": "Start a new thread", - - "AriaPostMeta": "Post meta", - "AriaDeletePost": "Delete this post", - "AriaRespondIntoThread": "Respond into this thread", - "AriaHome": "Home", - "PromptDeleteQuestion": "Delete post for all posterity?", - "Delete": "delete", - "Post": "post", - "Author": "Author", - "Responded": "responded", - "YourAnswer": "Your answer", - - "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, - "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, - "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, - "RegisterKeypairWarning": "This keypair will only be displayed once", - - "RegisterRules": `To register, you need to either belong to the <a href="https://webring.xxiivv.com">Merveilles Webring</a> or the <a href="https://merveilles.town">Merveilles Fediverse instance</a>`, - - "RegisterVerificationCode": "Your verification code is", - "RegisterVerificationInstructionsTitle": "Verification instructions", - // TODO (2022-09-20): make verification instructions another md file to load, pass path from config - "RegisterVerificationInstructions": `<p>You can use either your mastodon profile or your webring site to verify your registration.</p> + "Threads": "threads", + "ThreadNew": "new thread", + "ThreadThe": "the thread", + "Index": "index", + + "ThreadCreate": "Create thread", + "Title": "Title", + "Content": "Content", + "Create": "Create", + "TextareaPlaceholder": "Tabula rasa", + + "PasswordReset": "reset password", + "PasswordResetMessage": "You are logged in, log out to reset password using proof", + "PasswordResetSuccess": "Reset password—success!", + "PasswordResetSuccessMessage": "You reset your password!", + "PasswordResetSuccessLinkMessage": "Give it a try and", + + "RegisterMessage": "You already have an account (you are logged in with it).", + "RegisterLinkMessage": "Visit the", + "RegisterSuccess": "registered successfully", + + "ErrUnaccepted": "Unaccepted request", + "ErrThread404": "Thread not found", + "ErrThread404Message": "The thread does not exist (anymore?)", + "ErrGeneric404": "Page not found", + "ErrGeneric404Message": "The visited page does not exist (anymore?). Error code %d.", + + "NewThreadMessage": "Only members of this forum may create new threads", + "NewThreadLinkMessage": "If you are a member,", + "NewThreadCreateError": "Error creating thread", + "NewThreadCreateErrorMessage": "There was a database error when creating the thread, apologies.", + "ThreadStartNew": "Start a new thread", + + "AriaPostMeta": "Post meta", + "AriaDeletePost": "Delete this post", + "AriaRespondIntoThread": "Respond into this thread", + "AriaHome": "Home", + "PromptDeleteQuestion": "Delete post for all posterity?", + "Delete": "delete", + "Post": "post", + "Author": "Author", + "Responded": "responded", + "YourAnswer": "Your answer", + + "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, + "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, + "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, + "RegisterKeypairWarning": "This keypair will only be displayed once", + + "RegisterRules": `To register, you need to either belong to the <a href="https://webring.xxiivv.com">Merveilles Webring</a> or the <a href="https://merveilles.town">Merveilles Fediverse instance</a>`, + + "RegisterVerificationCode": "Your verification code is", + "RegisterVerificationInstructionsTitle": "Verification instructions", + // TODO (2022-09-20): make verification instructions another md file to load, pass path from config + "RegisterVerificationInstructions": `<p>You can use either your mastodon profile or your webring site to verify your registration.</p> <ul> <li><b>Mastodon:</b> temporarily add a new metadata item to <a href="https://merveilles.town/settings/profile">your profile</a> containing the verification code displayed above. Pass your profile as the verification link.</li> @@ -212,16 +212,16 @@ var EspanolMexicano = map[string]string{ the verification code from above. Pass the link to the uploaded file as the verification link (make sure it is viewable in a browser).</li> </ul> `, - "RegisterVerificationLink": "Verification link", - "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">{{ .CommunityName }} Code of Conduct</a>`, - "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">read it</a>`, - - "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", - "PasswordResetUsernameQuestion": "First up: what was your username?", - "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, - "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, - "ToolInstructions": `tool instructions`, - "PasswordResetToolInstructions": fmt.Sprintf(` + "RegisterVerificationLink": "Verification link", + "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">{{ .CommunityName }} Code of Conduct</a>`, + "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="https://github.com/merveilles/Resources/blob/master/CONDUCT.md">read it</a>`, + + "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", + "PasswordResetUsernameQuestion": "First up: what was your username?", + "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, + "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, + "ToolInstructions": `tool instructions`, + "PasswordResetToolInstructions": fmt.Sprintf(` <ul> <li><a href="%s">Download the tool</a></li> <li>Run as:<br><code>pwtool --payload &lt;proof payload from above&gt; --keypair &lt;path to file with yr keypair from registration&gt;</code> @@ -230,14 +230,14 @@ var EspanolMexicano = map[string]string{ <li>(Remember to save your password :)</li> </ul> `, toolURL), - "GeneratePayload": "generate payload", - "Proof": "proof", - "NewPassword": "new password", - "ChangePassword": "change password", + "GeneratePayload": "generate payload", + "Proof": "proof", + "NewPassword": "new password", + "ChangePassword": "change password", } var translations = map[string]map[string]string{ - "English": English, + "English": English, "EspañolMexicano": EspanolMexicano, } diff --git a/run.go b/run.go @@ -36,7 +36,7 @@ func main() { // TODO (2022-01-10): somehow continually update veri sites by scraping merveilles webring sites || webring domain var allowlistLocation string var sessionKey string - var configPath string + var configPath string var dev bool flag.BoolVar(&dev, "dev", false, "trigger development mode") flag.StringVar(&allowlistLocation, "allowlist", "", "domains which can be used to read verification codes from during registration") @@ -50,6 +50,6 @@ func main() { } allowlist := readAllowlist(allowlistLocation) allowlist = append(allowlist, "merveilles.town") - config := util.ReadConfig(configPath) + config := util.ReadConfig(configPath) server.Serve(allowlist, sessionKey, dev, config) } diff --git a/server/server.go b/server/server.go @@ -15,13 +15,13 @@ import ( "strings" "time" - "cerca/i18n" "cerca/crypto" "cerca/database" cercaHTML "cerca/html" + "cerca/i18n" "cerca/server/session" + "cerca/types" "cerca/util" - "cerca/types" "github.com/carlmjohnson/requests" ) @@ -31,7 +31,7 @@ import ( * CommunityLogo * CommunityLink * ForumName -*/ + */ // TODO (2022-09-20): make verification instructions another md file to load, pass path from config /* @@ -39,7 +39,7 @@ import ( * registration rules * verification instructions * code of conduct link -*/ + */ /* TODO (2022-01-03): include csrf token via gorilla, or w/e, when rendering */ @@ -48,7 +48,7 @@ type TemplateData struct { QuickNav bool LoggedIn bool // TODO (2022-01-09): put this in a middleware || template function or sth? LoggedInID int - ForumName string + ForumName string Title string } @@ -127,8 +127,8 @@ func (h RequestHandler) IsLoggedIn(req *http.Request) (bool, int) { } var ( - translator = i18n.Init("EspañolMexicano") - community = i18n.Community{"Merveilles", "https://wiki.xxiivv.com/site/merveilles.html"} + translator = i18n.Init("EspañolMexicano") + community = i18n.Community{"Merveilles", "https://wiki.xxiivv.com/site/merveilles.html"} templateFuncs = template.FuncMap{ "formatDateTime": func(t time.Time) string { @@ -149,21 +149,21 @@ var ( } return t.Format("2006-01-02") }, - "translate": func(key string) string { - return translator.Translate(key) - }, - "translateWithData": func(key string) string { - return translator.TranslateWithData(key, community) - }, - "capitalize": util.Capitalize, - "tohtml": func (s string) template.HTML { - // use of this function is risky cause it interprets the passed in string and renders it as unescaped html. - // can allow for attacks! - // - // advice: only use on strings that come statically from within cerca code, never on titles that may contain user-submitted data - // :) - return (template.HTML)(s) - }, + "translate": func(key string) string { + return translator.Translate(key) + }, + "translateWithData": func(key string) string { + return translator.TranslateWithData(key, community) + }, + "capitalize": util.Capitalize, + "tohtml": func(s string) template.HTML { + // use of this function is risky cause it interprets the passed in string and renders it as unescaped html. + // can allow for attacks! + // + // advice: only use on strings that come statically from within cerca code, never on titles that may contain user-submitted data + // :) + return (template.HTML)(s) + }, } templates = template.Must(generateTemplates()) @@ -172,7 +172,7 @@ var ( func generateTemplates() (*template.Template, error) { views := []string{ "about", - "about-template", + "about-template", "footer", "generic-message", "head", @@ -205,8 +205,8 @@ func (h RequestHandler) renderView(res http.ResponseWriter, viewName string, dat } if data.ForumName == "" { - data.ForumName = "Forum" - } + data.ForumName = "Forum" + } view := fmt.Sprintf("%s.html", viewName) if err := templates.ExecuteTemplate(res, view, data); err != nil { @@ -318,7 +318,7 @@ func (h RequestHandler) LoginRoute(res http.ResponseWriter, req *http.Request) { } if err != nil { fmt.Println(err) - h.renderView(res, "login", TemplateData{Data: LoginData{FailedAttempt: true}, LoggedIn: loggedIn, Title: translator.Translate("Login")}) + h.renderView(res, "login", TemplateData{Data: LoginData{FailedAttempt: true}, LoggedIn: loggedIn, Title: translator.Translate("Login")}) return } // save user id in cookie @@ -349,7 +349,7 @@ func hasVerificationCode(link, verification string) bool { func (h RequestHandler) ResetPasswordRoute(res http.ResponseWriter, req *http.Request) { ed := util.Describe("password proof route") loggedIn, _ := h.IsLoggedIn(req) - title := util.Capitalize(translator.Translate("PasswordReset")) + title := util.Capitalize(translator.Translate("PasswordReset")) if loggedIn { data := GenericMessageData{ @@ -482,7 +482,7 @@ func (h RequestHandler) RegisterRoute(res http.ResponseWriter, req *http.Request ed := util.Describe("register route") loggedIn, _ := h.IsLoggedIn(req) if loggedIn { - // TODO (2022-09-20): translate + // TODO (2022-09-20): translate data := GenericMessageData{ Title: util.Capitalize(translator.Translate("Register")), Message: translator.Translate("RegisterMessage"), @@ -610,14 +610,14 @@ func (h RequestHandler) GenericRoute(res http.ResponseWriter, req *http.Request) func (h RequestHandler) AboutRoute(res http.ResponseWriter, req *http.Request) { loggedIn, _ := h.IsLoggedIn(req) - // TODO (2022-09-19): - // * make sure file exists - // * create function to output a prefilled version, using the Community name and CommunityLink - // * embed the prefilled version in the code using golang's goembed - b, err := os.ReadFile("./about.md") - util.Check(err, "about route: open about.md") - input := util.Markup(template.HTML(b)) - h.renderView(res, "about-template", TemplateData{Data: input, LoggedIn: loggedIn, Title: translator.Translate("About")}) + // TODO (2022-09-19): + // * make sure file exists + // * create function to output a prefilled version, using the Community name and CommunityLink + // * embed the prefilled version in the code using golang's goembed + b, err := os.ReadFile("./about.md") + util.Check(err, "about route: open about.md") + input := util.Markup(template.HTML(b)) + h.renderView(res, "about-template", TemplateData{Data: input, LoggedIn: loggedIn, Title: translator.Translate("About")}) } func (h RequestHandler) RobotsRoute(res http.ResponseWriter, req *http.Request) { @@ -629,7 +629,7 @@ func (h RequestHandler) NewThreadRoute(res http.ResponseWriter, req *http.Reques switch req.Method { // Handle GET (=> want to start a new thread) case "GET": - // TODO (2022-09-20): translate + // TODO (2022-09-20): translate if !loggedIn { title := translator.Translate("NotLoggedIn") data := GenericMessageData{ @@ -723,7 +723,7 @@ func (h RequestHandler) DeletePostRoute(res http.ResponseWriter, req *http.Reque func Serve(allowlist []string, sessionKey string, isdev bool, conf types.Config) { port := ":8272" dir := "./data/" - config = conf + config = conf if isdev { developing = true @@ -770,7 +770,7 @@ func (u *CercaForum) directory() string { // of net.Listener. func NewServer(allowlist []string, sessionKey, dir string) (*CercaForum, error) { s := &CercaForum{ - ServeMux: http.ServeMux{}, + ServeMux: http.ServeMux{}, Directory: dir, } diff --git a/types/types.go b/types/types.go @@ -1,34 +1,34 @@ package types type Config struct { - // for internal use - Files map[string]string - // use as: - // config.Files["about"] -> about markdown - // config.Files["rules"] -> rules explanation markdown - // config.Files["verification"] -> verification explanation - - Community struct { - Name string - Link string - ConductLink string - } `json:"general"` - - Theme struct { - Background string - Foreground string - Links string - } `json:"theme"` - - Documents struct { - LogoPath string - AboutPath string - RegisterRulesPath string - VerificationExplanationPath string - } `json:"documents"` + // for internal use + Files map[string]string + // use as: + // config.Files["about"] -> about markdown + // config.Files["rules"] -> rules explanation markdown + // config.Files["verification"] -> verification explanation + + Community struct { + Name string + Link string + ConductLink string + } `json:"general"` + + Theme struct { + Background string + Foreground string + Links string + } `json:"theme"` + + Documents struct { + LogoPath string + AboutPath string + RegisterRulesPath string + VerificationExplanationPath string + } `json:"documents"` } -/* +/* config.Community.Name config.Community.Link config.Community.ConductLink diff --git a/util/util.go b/util/util.go @@ -1,25 +1,25 @@ package util import ( - "bytes" + "bytes" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json" "fmt" "html/template" "log" "net/http" "net/url" + "os" "strconv" "strings" - "os" - "github.com/komkom/toml" "github.com/gomarkdown/markdown" + "github.com/komkom/toml" "github.com/microcosm-cc/bluemonday" - "cerca/types" - "cerca/defaults" + "cerca/defaults" + "cerca/types" ) /* util.Eout example invocations @@ -137,29 +137,29 @@ func GetURLPortion(req *http.Request, index int) (int, bool) { return desiredID, true } -func Capitalize (s string) string { - return strings.ToUpper(string(s[0])) + s[1:] +func Capitalize(s string) string { + return strings.ToUpper(string(s[0])) + s[1:] } func CheckFileExists(filepath string, defaultContent string) { - // check if file exists - // if it doesn't: - // write the default contents to the filepath + // check if file exists + // if it doesn't: + // write the default contents to the filepath } -// TODO (2022-09-21): +// TODO (2022-09-21): // * DONE go:embed sample-config.toml ---> defaults.DEFAULT_<x> // * util.checkFileExists(path, mockContents) func ReadConfig(confpath string) types.Config { - data, err := os.ReadFile(confpath) - ed := Describe("config") - ed.Check(err, "read file") + data, err := os.ReadFile(confpath) + ed := Describe("config") + ed.Check(err, "read file") - var conf types.Config - decoder := json.NewDecoder(toml.New(bytes.NewBuffer(data))) + var conf types.Config + decoder := json.NewDecoder(toml.New(bytes.NewBuffer(data))) - err = decoder.Decode(&conf) - ed.Check(err, "decode toml with json decoder") + err = decoder.Decode(&conf) + ed.Check(err, "decode toml with json decoder") - return conf + return conf }