1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-11-12 21:30:10 +00:00

Compare commits

..

No commits in common. "8113f794ab46e9899a386fd907623e282b4d590e" and "ebacfd43be5e292248a5e175afc56aac7e08c17c" have entirely different histories.

7 changed files with 97 additions and 133 deletions

View File

@ -16,7 +16,7 @@ func (app *appContext) checkInvites() {
currentTime := time.Now() currentTime := time.Now()
app.storage.loadInvites() app.storage.loadInvites()
changed := false changed := false
for code, data := range app.storage.GetInvites() { for code, data := range app.storage.invites {
expiry := data.ValidTill expiry := data.ValidTill
if !currentTime.After(expiry) { if !currentTime.After(expiry) {
continue continue
@ -54,7 +54,7 @@ func (app *appContext) checkInvites() {
wait.Wait() wait.Wait()
} }
changed = true changed = true
app.storage.DeleteInvitesKey(code) delete(app.storage.invites, code)
} }
if changed { if changed {
app.storage.storeInvites() app.storage.storeInvites()
@ -65,7 +65,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
currentTime := time.Now() currentTime := time.Now()
app.storage.loadInvites() app.storage.loadInvites()
changed := false changed := false
inv, match := app.storage.GetInvitesKey(code) inv, match := app.storage.invites[code]
if !match { if !match {
return false return false
} }
@ -105,21 +105,21 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
} }
changed = true changed = true
match = false match = false
app.storage.DeleteInvitesKey(code) delete(app.storage.invites, code)
} else if used { } else if used {
changed = true changed = true
del := false del := false
newInv := inv newInv := inv
if newInv.RemainingUses == 1 { if newInv.RemainingUses == 1 {
del = true del = true
app.storage.DeleteInvitesKey(code) delete(app.storage.invites, code)
} else if newInv.RemainingUses != 0 { } else if newInv.RemainingUses != 0 {
// 0 means infinite i guess? // 0 means infinite i guess?
newInv.RemainingUses-- newInv.RemainingUses--
} }
newInv.UsedBy = append(newInv.UsedBy, []string{username, strconv.FormatInt(currentTime.Unix(), 10)}) newInv.UsedBy = append(newInv.UsedBy, []string{username, strconv.FormatInt(currentTime.Unix(), 10)})
if !del { if !del {
app.storage.SetInvitesKey(code, newInv) app.storage.invites[code] = newInv
} }
} }
if changed { if changed {
@ -219,7 +219,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
invite.Profile = "Default" invite.Profile = "Default"
} }
} }
app.storage.SetInvitesKey(inviteCode, invite) app.storage.invites[inviteCode] = invite
app.storage.storeInvites() app.storage.storeInvites()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -236,7 +236,7 @@ func (app *appContext) GetInvites(gc *gin.Context) {
app.storage.loadInvites() app.storage.loadInvites()
app.checkInvites() app.checkInvites()
var invites []inviteDTO var invites []inviteDTO
for code, inv := range app.storage.GetInvites() { for code, inv := range app.storage.invites {
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime) _, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
invite := inviteDTO{ invite := inviteDTO{
Code: code, Code: code,
@ -335,9 +335,9 @@ func (app *appContext) SetProfile(gc *gin.Context) {
respond(500, "Profile not found", gc) respond(500, "Profile not found", gc)
return return
} }
inv, _ := app.storage.GetInvitesKey(req.Invite) inv := app.storage.invites[req.Invite]
inv.Profile = req.Profile inv.Profile = req.Profile
app.storage.SetInvitesKey(req.Invite, inv) app.storage.invites[req.Invite] = inv
app.storage.storeInvites() app.storage.storeInvites()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -359,7 +359,7 @@ func (app *appContext) SetNotify(gc *gin.Context) {
app.debug.Printf("%s: Notification settings change requested", code) app.debug.Printf("%s: Notification settings change requested", code)
app.storage.loadInvites() app.storage.loadInvites()
app.storage.loadEmails() app.storage.loadEmails()
invite, ok := app.storage.GetInvitesKey(code) invite, ok := app.storage.invites[code]
if !ok { if !ok {
app.err.Printf("%s Notification setting change failed: Invalid code", code) app.err.Printf("%s Notification setting change failed: Invalid code", code)
respond(400, "Invalid invite code", gc) respond(400, "Invalid invite code", gc)
@ -398,7 +398,7 @@ func (app *appContext) SetNotify(gc *gin.Context) {
changed = true changed = true
} }
if changed { if changed {
app.storage.SetInvitesKey(code, invite) app.storage.invites[code] = invite
} }
} }
if changed { if changed {
@ -419,9 +419,9 @@ func (app *appContext) DeleteInvite(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Printf("%s: Deletion requested", req.Code) app.debug.Printf("%s: Deletion requested", req.Code)
var ok bool var ok bool
_, ok = app.storage.GetInvitesKey(req.Code) _, ok = app.storage.invites[req.Code]
if ok { if ok {
app.storage.DeleteInvitesKey(req.Code) delete(app.storage.invites, req.Code)
app.storage.storeInvites() app.storage.storeInvites()
app.info.Printf("%s: Invite deleted", req.Code) app.info.Printf("%s: Invite deleted", req.Code)
respondBool(200, true, gc) respondBool(200, true, gc)

View File

@ -460,7 +460,7 @@ func (app *appContext) TelegramVerified(gc *gin.Context) {
// @tags Other // @tags Other
func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) { func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
@ -484,7 +484,7 @@ func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
// @tags Other // @tags Other
func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) { func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
@ -513,7 +513,7 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) {
return return
} }
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
@ -537,7 +537,7 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) {
// @tags Other // @tags Other
func (app *appContext) MatrixSendPIN(gc *gin.Context) { func (app *appContext) MatrixSendPIN(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
@ -575,7 +575,7 @@ func (app *appContext) MatrixSendPIN(gc *gin.Context) {
// @tags Other // @tags Other
func (app *appContext) MatrixCheckPIN(gc *gin.Context) { func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
if _, ok := app.storage.GetInvitesKey(code); !ok { if _, ok := app.storage.invites[code]; !ok {
app.debug.Println("Matrix: Invite code was invalid") app.debug.Println("Matrix: Invite code was invalid")
respondBool(401, false, gc) respondBool(401, false, gc)
return return

View File

@ -227,10 +227,14 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
} }
if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed { if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed {
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"valid": true, "valid": true,
"invite": req.Code, "invite": req.Code,
"exp": time.Now().Add(30 * time.Minute).Unix(), "email": req.Email,
"type": "confirmation", "username": req.Username,
"password": req.Password,
"telegramPIN": req.TelegramPIN,
"exp": time.Now().Add(time.Hour * 12).Unix(),
"type": "confirmation",
} }
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET"))) key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
@ -242,17 +246,10 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return return
} }
if app.ConfirmationKeys == nil { inv := app.storage.invites[req.Code]
app.ConfirmationKeys = map[string]map[string]newUserDTO{} inv.Keys = append(inv.Keys, key)
} app.storage.invites[req.Code] = inv
cKeys, ok := app.ConfirmationKeys[req.Code] app.storage.storeInvites()
if !ok {
cKeys = map[string]newUserDTO{}
}
cKeys[key] = req
app.confirmationKeysLock.Lock()
app.ConfirmationKeys[req.Code] = cKeys
app.confirmationKeysLock.Unlock()
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: Email confirmation required", req.Code) app.debug.Printf("%s: Email confirmation required", req.Code)
respond(401, "confirmEmail", gc) respond(401, "confirmEmail", gc)
@ -279,7 +276,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
return return
} }
app.storage.loadProfiles() app.storage.loadProfiles()
invite, _ := app.storage.GetInvitesKey(req.Code) invite := app.storage.invites[req.Code]
app.checkInvite(req.Code, true, req.Username) app.checkInvite(req.Code, true, req.Username)
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) { if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) {
for address, settings := range invite.Notify { for address, settings := range invite.Notify {

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}"> <html lang="en" class="{{ .cssClass }}">
<head> <head>
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css"> <link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }} {{ template "header.html" . }}
<title>Invalid Code - jfa-go</title> <title>Invalid Code - jfa-go</title>
</head> </head>

45
main.go
View File

@ -17,7 +17,6 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
@ -91,29 +90,27 @@ type appContext struct {
adminUsers []User adminUsers []User
invalidTokens []string invalidTokens []string
// Keeping jf name because I can't think of a better one // Keeping jf name because I can't think of a better one
jf *mediabrowser.MediaBrowser jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser authJf *mediabrowser.MediaBrowser
ombi *ombi.Ombi ombi *ombi.Ombi
datePattern string datePattern string
timePattern string timePattern string
storage Storage storage Storage
validator Validator validator Validator
email *Emailer email *Emailer
telegram *TelegramDaemon telegram *TelegramDaemon
discord *DiscordDaemon discord *DiscordDaemon
matrix *MatrixDaemon matrix *MatrixDaemon
info, debug, err *logger.Logger info, debug, err *logger.Logger
host string host string
port int port int
version string version string
URLBase string URLBase string
updater *Updater updater *Updater
newUpdate bool // Whether whatever's in update is new. newUpdate bool // Whether whatever's in update is new.
tag Tag tag Tag
update Update update Update
internalPWRs map[string]InternalPWR internalPWRs map[string]InternalPWR
ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex
} }
func generateSecret(length int) (string, error) { func generateSecret(length int) (string, error) {

View File

@ -167,39 +167,6 @@ func (st *Storage) DeleteMatrixKey(k string) {
st.matrixLock.Unlock() st.matrixLock.Unlock()
} }
// GetInvites returns a copy of the store.
func (st *Storage) GetInvites() Invites {
if st.invites == nil {
st.invites = Invites{}
}
return st.invites
}
// GetInvitesKey returns the value stored in the store's key.
func (st *Storage) GetInvitesKey(k string) (Invite, bool) {
v, ok := st.invites[k]
return v, ok
}
// SetInvitesKey stores value v in key k.
func (st *Storage) SetInvitesKey(k string, v Invite) {
st.invitesLock.Lock()
if st.invites == nil {
st.invites = Invites{}
}
st.invites[k] = v
st.storeInvites()
st.invitesLock.Unlock()
}
// DeleteInvitesKey deletes value at key k.
func (st *Storage) DeleteInvitesKey(k string) {
st.invitesLock.Lock()
delete(st.invites, k)
st.storeInvites()
st.invitesLock.Unlock()
}
type TelegramUser struct { type TelegramUser struct {
ChatID int64 ChatID int64
Username string Username string
@ -278,6 +245,7 @@ type Invite struct {
Notify map[string]map[string]bool `json:"notify"` Notify map[string]map[string]bool `json:"notify"`
Profile string `json:"profile"` Profile string `json:"profile"`
Label string `json:"label,omitempty"` Label string `json:"label,omitempty"`
Keys []string `json:"keys,omitempty"`
Captchas map[string]*captcha.Data // Map of Captcha IDs to answers Captchas map[string]*captcha.Data // Map of Captcha IDs to answers
} }
@ -964,10 +932,14 @@ func (st *Storage) loadLangTelegram(filesystems ...fs.FS) error {
type Invites map[string]Invite type Invites map[string]Invite
func (st *Storage) loadInvites() error { func (st *Storage) loadInvites() error {
st.invitesLock.Lock()
defer st.invitesLock.Unlock()
return loadJSON(st.invite_path, &st.invites) return loadJSON(st.invite_path, &st.invites)
} }
func (st *Storage) storeInvites() error { func (st *Storage) storeInvites() error {
st.invitesLock.Lock()
defer st.invitesLock.Unlock()
return storeJSON(st.invite_path, st.invites) return storeJSON(st.invite_path, st.invites)
} }

View File

@ -342,10 +342,9 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
func (app *appContext) GetCaptcha(gc *gin.Context) { func (app *appContext) GetCaptcha(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
captchaID := gc.Param("captchaID") captchaID := gc.Param("captchaID")
inv, ok := app.storage.GetInvitesKey(code) inv, ok := app.storage.invites[code]
if !ok { if !ok {
gcHTML(gc, 404, "invalidCode.html", gin.H{ gcHTML(gc, 404, "invalidCode.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
@ -377,10 +376,9 @@ func (app *appContext) GetCaptcha(gc *gin.Context) {
// @tags Users // @tags Users
func (app *appContext) GenCaptcha(gc *gin.Context) { func (app *appContext) GenCaptcha(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
inv, ok := app.storage.GetInvitesKey(code) inv, ok := app.storage.invites[code]
if !ok { if !ok {
gcHTML(gc, 404, "invalidCode.html", gin.H{ gcHTML(gc, 404, "invalidCode.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
@ -397,7 +395,8 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
} }
captchaID := genAuthToken() captchaID := genAuthToken()
inv.Captchas[captchaID] = capt inv.Captchas[captchaID] = capt
app.storage.SetInvitesKey(code, inv) app.storage.invites[code] = inv
app.storage.storeInvites()
gc.JSON(200, genCaptchaDTO{captchaID}) gc.JSON(200, genCaptchaDTO{captchaID})
return return
} }
@ -406,7 +405,7 @@ func (app *appContext) verifyCaptcha(code, id, text string) bool {
reCAPTCHA := app.config.Section("captcha").Key("recaptcha").MustBool(false) reCAPTCHA := app.config.Section("captcha").Key("recaptcha").MustBool(false)
if !reCAPTCHA { if !reCAPTCHA {
// internal CAPTCHA // internal CAPTCHA
inv, ok := app.storage.GetInvitesKey(code) inv, ok := app.storage.invites[code]
if !ok || inv.Captchas == nil { if !ok || inv.Captchas == nil {
app.debug.Printf("Couldn't find invite \"%s\"", code) app.debug.Printf("Couldn't find invite \"%s\"", code)
return false return false
@ -473,10 +472,9 @@ func (app *appContext) VerifyCaptcha(gc *gin.Context) {
code := gc.Param("invCode") code := gc.Param("invCode")
captchaID := gc.Param("captchaID") captchaID := gc.Param("captchaID")
text := gc.Param("text") text := gc.Param("text")
inv, ok := app.storage.GetInvitesKey(code) inv, ok := app.storage.invites[code]
if !ok { if !ok {
gcHTML(gc, 404, "invalidCode.html", gin.H{ gcHTML(gc, 404, "invalidCode.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
@ -505,10 +503,9 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
lang := app.getLang(gc, FormPage, app.storage.lang.chosenUserLang) lang := app.getLang(gc, FormPage, app.storage.lang.chosenUserLang)
/* Don't actually check if the invite is valid, just if it exists, just so the page loads quicker. Invite is actually checked on submit anyway. */ /* Don't actually check if the invite is valid, just if it exists, just so the page loads quicker. Invite is actually checked on submit anyway. */
// if app.checkInvite(code, false, "") { // if app.checkInvite(code, false, "") {
inv, ok := app.storage.GetInvitesKey(code) inv, ok := app.storage.invites[code]
if !ok { if !ok {
gcHTML(gc, 404, "invalidCode.html", gin.H{ gcHTML(gc, 404, "invalidCode.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
@ -516,27 +513,23 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
return return
} }
if key := gc.Query("key"); key != "" && app.config.Section("email_confirmation").Key("enabled").MustBool(false) { if key := gc.Query("key"); key != "" && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
validKey := false
keyIndex := -1
for i, k := range inv.Keys {
if k == key {
validKey = true
keyIndex = i
break
}
}
fail := func() { fail := func() {
gcHTML(gc, 404, "404.html", gin.H{ gcHTML(gc, 404, "404.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
}) })
} }
var req newUserDTO if !validKey {
if app.ConfirmationKeys == nil {
fail()
return
}
invKeys, ok := app.ConfirmationKeys[code]
if !ok {
fail()
return
}
req, ok = invKeys[key]
if !ok {
fail() fail()
return return
} }
@ -547,17 +540,26 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
return return
} }
claims, ok := token.Claims.(jwt.MapClaims) claims, ok := token.Claims.(jwt.MapClaims)
expiry := time.Unix(int64(claims["exp"].(float64)), 0) expiryUnix := int64(claims["exp"].(float64))
if err != nil {
fail()
app.err.Printf("Failed to parse key expiry: %s", err)
return
}
expiry := time.Unix(expiryUnix, 0)
if !(ok && token.Valid && claims["invite"].(string) == code && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) { if !(ok && token.Valid && claims["invite"].(string) == code && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
fail() fail()
app.debug.Printf("Invalid key") app.debug.Printf("Invalid key")
return return
} }
f, success := app.newUser(req, true) req := newUserDTO{
Email: claims["email"].(string),
Username: claims["username"].(string),
Password: claims["password"].(string),
Code: claims["invite"].(string),
}
_, success := app.newUser(req, true)
if !success { if !success {
app.err.Printf("Failed to create new user")
// Not meant for us. Calling this will be a mess, but at least it might give us some information.
f(gc)
fail() fail()
return return
} }
@ -566,25 +568,22 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
gc.Redirect(301, jfLink) gc.Redirect(301, jfLink)
} else { } else {
gcHTML(gc, http.StatusOK, "create-success.html", gin.H{ gcHTML(gc, http.StatusOK, "create-success.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion,
"strings": app.storage.lang.User[lang].Strings, "strings": app.storage.lang.User[lang].Strings,
"successMessage": app.config.Section("ui").Key("success_message").String(), "successMessage": app.config.Section("ui").Key("success_message").String(),
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
"jfLink": jfLink, "jfLink": jfLink,
}) })
} }
delete(invKeys, key) inv, ok := app.storage.invites[code]
app.confirmationKeysLock.Lock() if ok {
app.ConfirmationKeys[code] = invKeys l := len(inv.Keys)
app.confirmationKeysLock.Unlock() inv.Keys[l-1], inv.Keys[keyIndex] = inv.Keys[keyIndex], inv.Keys[l-1]
app.storage.invites[code] = inv
}
return return
} }
email := "" email := app.storage.invites[code].SendTo
if invite, ok := app.storage.GetInvitesKey(code); ok {
email = invite.SendTo
}
if strings.Contains(email, "Failed") || !strings.Contains(email, "@") { if strings.Contains(email, "Failed") || !strings.Contains(email, "@") {
email = "" email = ""
} }
@ -658,7 +657,6 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
func (app *appContext) NoRouteHandler(gc *gin.Context) { func (app *appContext) NoRouteHandler(gc *gin.Context) {
app.pushResources(gc, OtherPage) app.pushResources(gc, OtherPage)
gcHTML(gc, 404, "404.html", gin.H{ gcHTML(gc, 404, "404.html", gin.H{
"urlBase": app.getURLBase(gc),
"cssClass": app.cssClass, "cssClass": app.cssClass,
"cssVersion": cssVersion, "cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),