Skip to content

Add feature to add custom css/js instead of overwriting all content #248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ internal/webserver/web/e2e.wasm
internal/webserver/web/static/js/wasm_exec.js
internal/webserver/web/static/js/min/wasm_exec.min.js
.vendor/
custom/
95 changes: 95 additions & 0 deletions internal/webserver/CustomStaticContent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package webserver

import (
"bufio"
"fmt"
"github.com/NYTimes/gziphandler"
"github.com/forceu/gokapi/internal/helper"
"net/http"
"os"
"strconv"
"strings"
)

const pathCustomFolder = "custom/"
const pathCustomCss = pathCustomFolder + "custom.css"
const pathCustomPublicJs = pathCustomFolder + "public.js"
const pathCustomAdminJs = pathCustomFolder + "admin.js"
const pathCustomVersioning = pathCustomFolder + "version.txt"

type customStatic struct {
Version string
CustomFolderExists bool
UseCustomCss bool
UseCustomPublicJs bool
UseCustomAdminJs bool
}

func loadCustomCssJsInfo() {
customStaticInfo = customStatic{}
folderExists := helper.FolderExists(pathCustomFolder)
customStaticInfo.CustomFolderExists = folderExists
if !folderExists {
return
}
customStaticInfo.Version = strconv.Itoa(readCustomStaticVersion())
customStaticInfo.UseCustomCss = helper.FileExists(pathCustomCss)
customStaticInfo.UseCustomPublicJs = helper.FileExists(pathCustomPublicJs)
customStaticInfo.UseCustomAdminJs = helper.FileExists(pathCustomAdminJs)
}

func addMuxForCustomContent(mux *http.ServeMux) {
if !customStaticInfo.CustomFolderExists {
return
}
fmt.Println("Serving custom static content")
// Serve the user-created "custom" folder to /custom
mux.Handle("/custom/", http.StripPrefix("/custom/", http.FileServer(http.Dir(pathCustomFolder))))
// Allow versioning to prevent caching old version
if customStaticInfo.UseCustomCss {
mux.Handle("/custom/custom.v"+customStaticInfo.Version+".css", gziphandler.GzipHandler(http.HandlerFunc(serveCustomCss)))
}
if customStaticInfo.UseCustomPublicJs {
mux.Handle("/custom/public.v"+customStaticInfo.Version+".js", gziphandler.GzipHandler(http.HandlerFunc(serveCustomPublicJs)))
}
if customStaticInfo.UseCustomAdminJs {
mux.Handle("/custom/admin.v"+customStaticInfo.Version+".js", gziphandler.GzipHandler(http.HandlerFunc(serveCustomAdminJs)))
}
}

func serveCustomCss(w http.ResponseWriter, r *http.Request) {
serveCustomFile(pathCustomCss, w, r)
}
func serveCustomPublicJs(w http.ResponseWriter, r *http.Request) {
serveCustomFile(pathCustomPublicJs, w, r)
}
func serveCustomAdminJs(w http.ResponseWriter, r *http.Request) {
serveCustomFile(pathCustomAdminJs, w, r)
}

func serveCustomFile(filePath string, w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "public, max-age=100800") // 2 days
http.ServeFile(w, r, filePath)
}

func readCustomStaticVersion() int {
if !helper.FileExists(pathCustomVersioning) {
return 0
}
file, err := os.Open(pathCustomVersioning)
if err != nil {
fmt.Println(err)
return 0
}
defer file.Close()
sc := bufio.NewScanner(file)
if !sc.Scan() {
return 0
}
line := strings.TrimSpace(sc.Text())
version, err := strconv.Atoi(line)
if err != nil {
fmt.Println("Content of " + pathCustomVersioning + " must be numerical")
}
return version
}
79 changes: 52 additions & 27 deletions internal/webserver/Webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,19 @@ var wasmDownloadFile embed.FS
//go:embed web/e2e.wasm
var wasmE2EFile embed.FS

const timeOutWebserverRead = 12 * time.Hour
const timeOutWebserverRead = 2 * time.Hour
const timeOutWebserverWrite = 12 * time.Hour

// Variable containing all parsed templates
// templateFolder contains all parsed templates
var templateFolder *template.Template

// customStaticInfo is passed to all templates, so custom CSS or JS can be embedded
var customStaticInfo customStatic

// imageExpiredPicture is sent for an expired hotlink
var imageExpiredPicture []byte

// srv is the web server that is used for this module
var srv http.Server

// Start the webserver on the port set in the config
Expand All @@ -80,15 +85,10 @@ func Start() {
var err error

mux := http.NewServeMux()

if helper.FolderExists("static") {
fmt.Println("Found folder 'static', using local folder instead of internal static folder")
mux.Handle("/", http.FileServer(http.Dir("static")))
} else {
mux.Handle("/", http.FileServer(http.FS(webserverDir)))
}
loadCustomCssJsInfo()
loadExpiryImage()

mux.Handle("/", http.FileServer(http.FS(webserverDir)))
mux.HandleFunc("/admin", requireLogin(showAdminMenu, true, false))
mux.HandleFunc("/api/", processApi)
mux.HandleFunc("/apiKeys", requireLogin(showApiAdmin, true, false))
Expand All @@ -113,10 +113,11 @@ func Start() {
mux.HandleFunc("/users", requireLogin(showUserAdmin, true, false))
mux.Handle("/main.wasm", gziphandler.GzipHandler(http.HandlerFunc(serveDownloadWasm)))
mux.Handle("/e2e.wasm", gziphandler.GzipHandler(http.HandlerFunc(serveE2EWasm)))

mux.HandleFunc("/d/{id}/{filename}", redirectFromFilename)
mux.HandleFunc("/dh/{id}/{filename}", downloadFileWithNameInUrl)

addMuxForCustomContent(mux)

if configuration.Get().Authentication.Method == models.AuthenticationOAuth2 {
oauth.Init(configuration.Get().ServerUrl, configuration.Get().Authentication)
mux.HandleFunc("/oauth-login", oauth.HandlerLogin)
Expand Down Expand Up @@ -255,7 +256,9 @@ func doLogout(w http.ResponseWriter, r *http.Request) {

// Handling of /index and redirecting to globalConfig.RedirectUrl
func showIndex(w http.ResponseWriter, r *http.Request) {
err := templateFolder.ExecuteTemplate(w, "index", genericView{RedirectUrl: configuration.Get().RedirectUrl, PublicName: configuration.Get().PublicName})
err := templateFolder.ExecuteTemplate(w, "index", genericView{RedirectUrl: configuration.Get().RedirectUrl,
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

Expand Down Expand Up @@ -292,7 +295,8 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
err = templateFolder.ExecuteTemplate(w, "changepw",
genericView{PublicName: configuration.Get().PublicName,
MinPasswordLength: configuration.MinLengthPassword,
ErrorMessage: errMessage})
ErrorMessage: errMessage,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

Expand Down Expand Up @@ -323,25 +327,33 @@ func showError(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Has("key") {
errorReason = wrongCipher
}
err := templateFolder.ExecuteTemplate(w, "error", genericView{ErrorId: errorReason, PublicName: configuration.Get().PublicName})
err := templateFolder.ExecuteTemplate(w, "error", genericView{
ErrorId: errorReason,
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

// Handling of /error-auth
func showErrorAuth(w http.ResponseWriter, r *http.Request) {
err := templateFolder.ExecuteTemplate(w, "error_auth", genericView{PublicName: configuration.Get().PublicName})
err := templateFolder.ExecuteTemplate(w, "error_auth", genericView{
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

// Handling of /error-header
func showErrorHeader(w http.ResponseWriter, r *http.Request) {
err := templateFolder.ExecuteTemplate(w, "error_auth_header", genericView{PublicName: configuration.Get().PublicName})
err := templateFolder.ExecuteTemplate(w, "error_auth_header", genericView{
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

// Handling of /error-oauth
func showErrorIntOAuth(w http.ResponseWriter, r *http.Request) {
view := oauthErrorView{PublicName: configuration.Get().PublicName}
view := oauthErrorView{PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo}
view.IsAuthDenied = r.URL.Query().Get("isDenied") == "true"
view.ErrorProvidedName = r.URL.Query().Get("error")
view.ErrorProvidedMessage = r.URL.Query().Get("error_description")
Expand All @@ -352,7 +364,9 @@ func showErrorIntOAuth(w http.ResponseWriter, r *http.Request) {

// Handling of /forgotpw
func forgotPassword(w http.ResponseWriter, r *http.Request) {
err := templateFolder.ExecuteTemplate(w, "forgotpw", genericView{PublicName: configuration.Get().PublicName})
err := templateFolder.ExecuteTemplate(w, "forgotpw", genericView{
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

Expand All @@ -363,7 +377,7 @@ func showApiAdmin(w http.ResponseWriter, r *http.Request) {
if err != nil {
panic(err)
}
view := (&UploadView{}).convertGlobalConfig(ViewAPI, userId)
view := (&AdminView{}).convertGlobalConfig(ViewAPI, userId)
err = templateFolder.ExecuteTemplate(w, "api", view)
helper.CheckIgnoreTimeout(err)
}
Expand All @@ -375,7 +389,7 @@ func showUserAdmin(w http.ResponseWriter, r *http.Request) {
if err != nil {
panic(err)
}
view := (&UploadView{}).convertGlobalConfig(ViewUsers, userId)
view := (&AdminView{}).convertGlobalConfig(ViewUsers, userId)
if !view.ActiveUser.HasPermissionManageUsers() || configuration.Get().Authentication.Method == models.AuthenticationDisabled {
redirect(w, "admin")
return
Expand Down Expand Up @@ -437,6 +451,7 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
User: user,
IsAdminView: false,
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo,
})
helper.CheckIgnoreTimeout(err)
}
Expand All @@ -448,6 +463,7 @@ type LoginView struct {
IsDownloadView bool
User string
PublicName string
CustomContent customStatic
}

// Handling of /d
Expand All @@ -474,6 +490,7 @@ func showDownload(w http.ResponseWriter, r *http.Request) {
BaseUrl: config.ServerUrl,
IsFailedLogin: false,
UsesHttps: configuration.UsesHttps(),
CustomContent: customStaticInfo,
}

if file.RequiresClientDecryption() {
Expand Down Expand Up @@ -600,7 +617,6 @@ func queryUrl(w http.ResponseWriter, r *http.Request, redirectUrl string) string
// Handling of /admin
// If user is authenticated, this menu lists all uploads and enables uploading new files
func showAdminMenu(w http.ResponseWriter, r *http.Request) {

user, err := authentication.GetUserFromRequest(r)
if err != nil {
panic(err)
Expand All @@ -613,7 +629,7 @@ func showAdminMenu(w http.ResponseWriter, r *http.Request) {
return
}
}
err = templateFolder.ExecuteTemplate(w, "admin", (&UploadView{}).convertGlobalConfig(ViewMain, user))
err = templateFolder.ExecuteTemplate(w, "admin", (&AdminView{}).convertGlobalConfig(ViewMain, user))
helper.CheckIgnoreTimeout(err)
}

Expand All @@ -624,7 +640,7 @@ func showLogs(w http.ResponseWriter, r *http.Request) {
if err != nil {
panic(err)
}
view := (&UploadView{}).convertGlobalConfig(ViewLogs, user)
view := (&AdminView{}).convertGlobalConfig(ViewLogs, user)
if !view.ActiveUser.HasPermissionManageLogs() {
redirect(w, "admin")
return
Expand All @@ -644,7 +660,10 @@ func showE2ESetup(w http.ResponseWriter, r *http.Request) {
panic(err)
}
e2einfo := database.GetEnd2EndInfo(user.Id)
err = templateFolder.ExecuteTemplate(w, "e2esetup", e2ESetupView{HasBeenSetup: e2einfo.HasBeenSetUp(), PublicName: configuration.Get().PublicName})
err = templateFolder.ExecuteTemplate(w, "e2esetup", e2ESetupView{
HasBeenSetup: e2einfo.HasBeenSetUp(),
PublicName: configuration.Get().PublicName,
CustomContent: customStaticInfo})
helper.CheckIgnoreTimeout(err)
}

Expand All @@ -663,17 +682,19 @@ type DownloadView struct {
ClientSideDecryption bool
EndToEndEncryption bool
UsesHttps bool
CustomContent customStatic
}

type e2ESetupView struct {
IsAdminView bool
IsDownloadView bool
HasBeenSetup bool
PublicName string
CustomContent customStatic
}

// UploadView contains parameters for the admin menu template
type UploadView struct {
// AdminView contains parameters for all admin related pages
type AdminView struct {
Items []models.FileApiOutput
ApiKeys []models.ApiKey
Users []userInfo
Expand All @@ -696,6 +717,7 @@ type UploadView struct {
ChunkSize int
MaxParallelUploads int
TimeNow int64
CustomContent customStatic
}

// getUserMap needs to return the map with pointers, otherwise template cannot call
Expand All @@ -720,16 +742,17 @@ const (
ViewUsers
)

// Converts the globalConfig variable to an UploadView struct to pass the infos to
// Converts the globalConfig variable to an AdminView struct to pass the infos to
// the admin template
func (u *UploadView) convertGlobalConfig(view int, user models.User) *UploadView {
func (u *AdminView) convertGlobalConfig(view int, user models.User) *AdminView {
var result []models.FileApiOutput
var resultApi []models.ApiKey

config := configuration.Get()
u.IsInternalAuth = config.Authentication.Method == models.AuthenticationInternal
u.ActiveUser = user
u.UserMap = getUserMap()
u.CustomContent = customStaticInfo
switch view {
case ViewMain:
for _, element := range database.GetAllMetadata() {
Expand Down Expand Up @@ -946,6 +969,7 @@ type genericView struct {
ErrorMessage string
ErrorId int
MinPasswordLength int
CustomContent customStatic
}

// A view containing parameters for an oauth error
Expand All @@ -957,4 +981,5 @@ type oauthErrorView struct {
ErrorGenericMessage string
ErrorProvidedName string
ErrorProvidedMessage string
CustomContent customStatic
}
3 changes: 3 additions & 0 deletions internal/webserver/web/templates/html_admin.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@
</script>
{{ end }}


{{ template "pagename" "UploadMenu"}}
{{ template "customjs" .}}


{{ template "footer" true}}
Expand Down
5 changes: 5 additions & 0 deletions internal/webserver/web/templates/html_api.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,10 @@
var canReplaceFiles = {{.ActiveUser.HasPermissionManageApi }};
var canManageUsers = {{.ActiveUser.HasPermissionManageApi }};
</script>


{{ template "pagename" "ApiOverview"}}
{{ template "customjs" .}}

{{ template "footer" true }}
{{ end }}
2 changes: 2 additions & 0 deletions internal/webserver/web/templates/html_changepw.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ function submitForm() {
}

</script>
{{ template "pagename" "ChangePw"}}
{{ template "customjs" .}}
{{ template "footer" }}
{{end}}
Loading