dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

settings.go (39059B)


      1 package handlers
      2 
      3 import (
      4 	"bytes"
      5 	"dkforest/pkg/config"
      6 	"dkforest/pkg/database"
      7 	dutils "dkforest/pkg/database/utils"
      8 	"dkforest/pkg/global"
      9 	"dkforest/pkg/managers"
     10 	"dkforest/pkg/utils"
     11 	hutils "dkforest/pkg/web/handlers/utils"
     12 	"errors"
     13 	"filippo.io/age"
     14 	armor1 "filippo.io/age/armor"
     15 	"fmt"
     16 	"github.com/asaskevich/govalidator"
     17 	"github.com/dustin/go-humanize"
     18 	"github.com/labstack/echo"
     19 	"github.com/sirupsen/logrus"
     20 	"image"
     21 	"io"
     22 	"net/http"
     23 	"regexp"
     24 	"sort"
     25 	"strings"
     26 	"time"
     27 )
     28 
     29 // SettingsChatHandler ...
     30 func SettingsChatHandler(c echo.Context) error {
     31 	authUser := c.Get("authUser").(*database.User)
     32 	var data settingsChatData
     33 	data.ActiveTab = "chat"
     34 	data.AllFonts = utils.GetFonts()
     35 	data.ChatColor = authUser.ChatColor
     36 	data.ChatBackgroundColor = authUser.ChatBackgroundColor
     37 	data.ChatFont = authUser.ChatFont
     38 	data.ChatItalic = authUser.ChatItalic
     39 	data.ChatBold = authUser.ChatBold
     40 	data.DateFormat = authUser.DateFormat
     41 	data.ChatReadMarkerEnabled = authUser.ChatReadMarkerEnabled
     42 	data.ChatReadMarkerColor = authUser.ChatReadMarkerColor
     43 	data.ChatReadMarkerSize = authUser.ChatReadMarkerSize
     44 	data.DisplayHellbanned = authUser.DisplayHellbanned
     45 	data.DisplayModerators = authUser.DisplayModerators
     46 	data.DisplayDeleteButton = authUser.DisplayDeleteButton
     47 	data.DisplayKickButton = authUser.DisplayKickButton
     48 	data.DisplayHellbanButton = authUser.DisplayHellbanButton
     49 	data.HideRightColumn = authUser.HideRightColumn
     50 	data.ChatBarAtBottom = authUser.ChatBarAtBottom
     51 	data.AutocompleteCommandsEnabled = authUser.AutocompleteCommandsEnabled
     52 	data.SpellcheckEnabled = authUser.SpellcheckEnabled
     53 	data.AfkIndicatorEnabled = authUser.AfkIndicatorEnabled
     54 	data.HideIgnoredUsersFromList = authUser.HideIgnoredUsersFromList
     55 	data.HellbanOpacity = float64(authUser.HellbanOpacity) / 100
     56 	data.CodeBlockHeight = authUser.CodeBlockHeight
     57 	data.RefreshRate = authUser.RefreshRate
     58 	data.NotifyNewMessage = authUser.NotifyNewMessage
     59 	data.NotifyTagged = authUser.NotifyTagged
     60 	data.NotifyPmmed = authUser.NotifyPmmed
     61 	data.NotifyNewMessageSound = authUser.NotifyNewMessageSound
     62 	data.NotifyTaggedSound = authUser.NotifyTaggedSound
     63 	data.NotifyPmmedSound = authUser.NotifyPmmedSound
     64 	data.Theme = authUser.Theme
     65 	data.NotifyChessGames = authUser.NotifyChessGames
     66 	data.NotifyChessMove = authUser.NotifyChessMove
     67 	data.UseStream = authUser.UseStream
     68 	data.UseStreamMenu = authUser.UseStreamMenu
     69 	data.UseStreamTopBar = authUser.UseStreamTopBar
     70 	data.DisplayAliveIndicator = authUser.DisplayAliveIndicator
     71 	data.ConfirmExternalLinks = authUser.ConfirmExternalLinks
     72 	data.ChessSoundsEnabled = authUser.ChessSoundsEnabled
     73 	data.PokerSoundsEnabled = authUser.PokerSoundsEnabled
     74 	data.ManualMultiline = authUser.ManualMultiline
     75 
     76 	if c.Request().Method == http.MethodGet {
     77 		return c.Render(http.StatusOK, "settings.chat", data)
     78 	}
     79 
     80 	// POST
     81 	formName := c.FormValue("formName")
     82 	if formName == "changeSettings" {
     83 		return changeSettingsForm(c, data)
     84 	}
     85 	return c.Render(http.StatusOK, "settings.chat", data)
     86 }
     87 
     88 func changeSettingsForm(c echo.Context, data settingsChatData) error {
     89 	authUser := c.Get("authUser").(*database.User)
     90 	db := c.Get("database").(*database.DkfDB)
     91 
     92 	data.RefreshRate = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("refresh_rate")), 5, 60)
     93 	data.ChatColor = c.Request().PostFormValue("chat_color")
     94 	data.ChatBackgroundColor = c.Request().PostFormValue("chat_background_color")
     95 	data.ChatFont = utils.DoParseInt64(c.Request().PostFormValue("chat_font"))
     96 	data.ChatBold = utils.DoParseBool(c.Request().PostFormValue("chat_bold"))
     97 	data.DateFormat = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("date_format")), 0, 4)
     98 	data.ChatReadMarkerEnabled = utils.DoParseBool(c.Request().PostFormValue("chat_read_marker_enabled"))
     99 	data.ChatReadMarkerColor = c.Request().PostFormValue("chat_read_marker_color")
    100 	data.ChatReadMarkerSize = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("chat_read_marker_size")), 1, 5)
    101 	data.DisplayHellbanned = utils.DoParseBool(c.Request().PostFormValue("display_hellbanned"))
    102 	data.DisplayModerators = utils.DoParseBool(c.Request().PostFormValue("display_moderators"))
    103 	data.DisplayKickButton = utils.DoParseBool(c.Request().PostFormValue("display_kick_button"))
    104 	data.DisplayDeleteButton = utils.DoParseBool(c.Request().PostFormValue("display_delete_button"))
    105 	data.DisplayHellbanButton = utils.DoParseBool(c.Request().PostFormValue("display_hellban_button"))
    106 	data.HideIgnoredUsersFromList = utils.DoParseBool(c.Request().PostFormValue("hide_ignored_users_from_list"))
    107 	data.HideRightColumn = utils.DoParseBool(c.Request().PostFormValue("hide_right_column"))
    108 	data.ChatBarAtBottom = utils.DoParseBool(c.Request().PostFormValue("chat_bar_at_bottom"))
    109 	data.AutocompleteCommandsEnabled = utils.DoParseBool(c.Request().PostFormValue("autocomplete_commands_enabled"))
    110 	data.SpellcheckEnabled = utils.DoParseBool(c.Request().PostFormValue("spellcheck_enabled"))
    111 	data.AfkIndicatorEnabled = utils.DoParseBool(c.Request().PostFormValue("afk_indicator_enabled"))
    112 	data.ChatItalic = utils.DoParseBool(c.Request().PostFormValue("chat_italic"))
    113 	data.NotifyNewMessage = utils.DoParseBool(c.Request().PostFormValue("notify_new_message"))
    114 	data.NotifyTagged = utils.DoParseBool(c.Request().PostFormValue("notify_tagged"))
    115 	data.NotifyPmmed = utils.DoParseBool(c.Request().PostFormValue("notify_pmmed"))
    116 	data.Theme = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("theme")), 0, 2)
    117 	data.NotifyChessGames = utils.DoParseBool(c.Request().PostFormValue("notify_chess_games"))
    118 	data.NotifyChessMove = utils.DoParseBool(c.Request().PostFormValue("notify_chess_move"))
    119 	data.UseStream = utils.DoParseBool(c.Request().PostFormValue("use_stream"))
    120 	data.UseStreamMenu = utils.DoParseBool(c.Request().PostFormValue("use_stream_menu"))
    121 	data.UseStreamTopBar = utils.DoParseBool(c.Request().PostFormValue("use_stream_top_bar"))
    122 	data.DisplayAliveIndicator = utils.DoParseBool(c.Request().PostFormValue("display_alive_indicator"))
    123 	data.ConfirmExternalLinks = utils.DoParseBool(c.Request().PostFormValue("confirm_external_links"))
    124 	data.ChessSoundsEnabled = utils.DoParseBool(c.Request().PostFormValue("chess_sounds_enabled"))
    125 	data.PokerSoundsEnabled = utils.DoParseBool(c.Request().PostFormValue("poker_sounds_enabled"))
    126 	data.HellbanOpacity = utils.DoParseF64(c.Request().PostFormValue("hellban_opacity"))
    127 	data.CodeBlockHeight = utils.DoParseInt64(c.Request().PostFormValue("code_block_height"))
    128 	data.ManualMultiline = utils.DoParseBool(c.Request().PostFormValue("manual_multiline"))
    129 	//data.NotifyNewMessageSound = utils.DoParseInt64(c.Request().PostFormValue("notify_new_message_sound"))
    130 	//data.NotifyTaggedSound = utils.DoParseInt64(c.Request().PostFormValue("notify_tagged_sound"))
    131 	//data.NotifyPmmedSound = utils.DoParseInt64(c.Request().PostFormValue("notify_pmmed_sound"))
    132 	colorRgx := regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
    133 	if !colorRgx.MatchString(data.ChatColor) {
    134 		data.Error = "Invalid color format (text)"
    135 		return c.Render(http.StatusOK, "settings.chat", data)
    136 	}
    137 	if !colorRgx.MatchString(data.ChatBackgroundColor) {
    138 		data.Error = "Invalid color format (background)"
    139 		return c.Render(http.StatusOK, "settings.chat", data)
    140 	}
    141 	if !colorRgx.MatchString(data.ChatReadMarkerColor) {
    142 		data.Error = "Invalid marker color format"
    143 		return c.Render(http.StatusOK, "settings.chat", data)
    144 	}
    145 	authUser.RefreshRate = data.RefreshRate
    146 	if authUser.CanChangeColor {
    147 		authUser.ChatColor = data.ChatColor
    148 	}
    149 	authUser.ChatBackgroundColor = data.ChatBackgroundColor
    150 	authUser.ChatFont = data.ChatFont
    151 	authUser.ChatItalic = data.ChatItalic
    152 	authUser.ChatBold = data.ChatBold
    153 	authUser.DateFormat = data.DateFormat
    154 	authUser.ChatReadMarkerEnabled = data.ChatReadMarkerEnabled
    155 	authUser.ChatReadMarkerColor = data.ChatReadMarkerColor
    156 	authUser.ChatReadMarkerSize = data.ChatReadMarkerSize
    157 	authUser.DisplayDeleteButton = data.DisplayDeleteButton
    158 	authUser.HideIgnoredUsersFromList = data.HideIgnoredUsersFromList
    159 	authUser.HideRightColumn = data.HideRightColumn
    160 	authUser.ChatBarAtBottom = data.ChatBarAtBottom
    161 	authUser.AutocompleteCommandsEnabled = data.AutocompleteCommandsEnabled
    162 	authUser.SpellcheckEnabled = data.SpellcheckEnabled
    163 	authUser.AfkIndicatorEnabled = data.AfkIndicatorEnabled
    164 	authUser.NotifyNewMessage = data.NotifyNewMessage
    165 	authUser.NotifyTagged = data.NotifyTagged
    166 	authUser.NotifyPmmed = data.NotifyPmmed
    167 	authUser.NotifyChessGames = data.NotifyChessGames
    168 	authUser.NotifyChessMove = data.NotifyChessMove
    169 	authUser.UseStream = data.UseStream
    170 	authUser.UseStreamMenu = data.UseStreamMenu
    171 	authUser.UseStreamTopBar = data.UseStreamTopBar
    172 	authUser.DisplayAliveIndicator = data.DisplayAliveIndicator
    173 	authUser.ConfirmExternalLinks = data.ConfirmExternalLinks
    174 	authUser.ChessSoundsEnabled = data.ChessSoundsEnabled
    175 	authUser.PokerSoundsEnabled = data.PokerSoundsEnabled
    176 	authUser.Theme = data.Theme
    177 	//authUser.NotifyNewMessageSound = data.NotifyNewMessageSound
    178 	//authUser.NotifyTaggedSound = data.NotifyTaggedSound
    179 	//authUser.NotifyPmmedSound = data.NotifyPmmedSound
    180 
    181 	authUser.CodeBlockHeight = utils.Clamp(data.CodeBlockHeight, 15, 300)
    182 	if authUser.CanSeeHB() {
    183 		authUser.HellbanOpacity = utils.Clamp(int64(data.HellbanOpacity*100), 0, 100)
    184 	}
    185 	if authUser.IsModerator() {
    186 		authUser.DisplayHellbanned = data.DisplayHellbanned
    187 		authUser.DisplayModerators = data.DisplayModerators
    188 		authUser.DisplayKickButton = data.DisplayKickButton
    189 		authUser.DisplayHellbanButton = data.DisplayHellbanButton
    190 	}
    191 	if authUser.CanUseMultiline {
    192 		authUser.ManualMultiline = data.ManualMultiline
    193 	}
    194 
    195 	if err := authUser.Save(db); err != nil {
    196 		logrus.Error(err)
    197 		data.Error = err.Error()
    198 		return c.Render(http.StatusOK, "settings.chat", data)
    199 	}
    200 
    201 	return c.Render(http.StatusOK, "flash", FlashResponse{Message: "Settings changed successfully", Redirect: "/settings/chat"})
    202 }
    203 
    204 func SettingsSecurityHandler(c echo.Context) error {
    205 	authUser := c.Get("authUser").(*database.User)
    206 	db := c.Get("database").(*database.DkfDB)
    207 	var data settingsSecurityData
    208 	data.ActiveTab = "security"
    209 	data.Logs, _ = db.GetSecurityLogs(authUser.ID)
    210 	return c.Render(http.StatusOK, "settings.security", data)
    211 }
    212 
    213 // SettingsAccountHandler ...
    214 func SettingsAccountHandler(c echo.Context) error {
    215 	authUser := c.Get("authUser").(*database.User)
    216 	var data settingsAccountData
    217 	data.AccountTooYoungErrorString = hutils.AccountTooYoungErr.Error()
    218 	data.ActiveTab = "account"
    219 	data.Username = authUser.Username
    220 	data.Email = authUser.Email
    221 	data.LastSeenPublic = authUser.LastSeenPublic
    222 	data.TerminateAllSessionsOnLogout = authUser.TerminateAllSessionsOnLogout
    223 	data.Website = authUser.Website
    224 
    225 	if c.Request().Method == http.MethodGet {
    226 		return c.Render(http.StatusOK, "settings.account", data)
    227 	}
    228 
    229 	// POST
    230 	formName := c.FormValue("formName")
    231 	switch formName {
    232 	case "changeUsername":
    233 		return changeUsernameForm(c, data)
    234 	case "editProfile":
    235 		return editProfileForm(c, data)
    236 	case "changeAvatar":
    237 		return changeAvatarForm(c, data)
    238 	default:
    239 		return c.Render(http.StatusOK, "settings.account", data)
    240 	}
    241 }
    242 
    243 func changeUsernameForm(c echo.Context, data settingsAccountData) error {
    244 	authUser := c.Get("authUser").(*database.User)
    245 	db := c.Get("database").(*database.DkfDB)
    246 	if !authUser.CanChangeUsername {
    247 		data.ErrorUsername = "Not allowed to change your username"
    248 		return c.Render(http.StatusOK, "settings.account", data)
    249 	}
    250 
    251 	username := database.Username(c.Request().PostFormValue("username"))
    252 	data.Username = username
    253 
    254 	if username == authUser.Username {
    255 		data.ErrorUsername = "username did not change"
    256 		return c.Render(http.StatusOK, "settings.account", data)
    257 	}
    258 
    259 	if err := db.CanRenameTo(authUser.Username, username); err != nil {
    260 		data.ErrorUsername = err.Error()
    261 		return c.Render(http.StatusOK, "settings.account", data)
    262 	}
    263 
    264 	managers.ActiveUsers.RemoveUser(authUser.ID)
    265 	authUser.Username = username
    266 	if err := db.DB().Save(authUser).Error; err != nil {
    267 		logrus.Error(err)
    268 		data.ErrorUsername = err.Error()
    269 		return c.Render(http.StatusOK, "settings.account", data)
    270 	}
    271 
    272 	db.CreateSecurityLog(authUser.ID, database.UsernameChangedSecurityLog)
    273 	return c.Render(http.StatusOK, "flash", FlashResponse{Message: "Username changed successfully", Redirect: "/settings/account"})
    274 }
    275 
    276 func editProfileForm(c echo.Context, data settingsAccountData) error {
    277 	authUser := c.Get("authUser").(*database.User)
    278 	db := c.Get("database").(*database.DkfDB)
    279 
    280 	email := c.Request().PostFormValue("email")
    281 	website := c.Request().PostFormValue("website")
    282 	lastSeenPublic := utils.DoParseBool(c.Request().PostFormValue("last_seen_public"))
    283 	terminateAllSessionsOnLogout := utils.DoParseBool(c.Request().PostFormValue("terminate_all_sessions_on_logout"))
    284 	data.Email = email
    285 	data.Website = website
    286 	data.LastSeenPublic = lastSeenPublic
    287 	data.TerminateAllSessionsOnLogout = terminateAllSessionsOnLogout
    288 
    289 	if data.Email != "" && !govalidator.IsEmail(data.Email) {
    290 		data.ErrorEmail = "invalid email"
    291 		return c.Render(http.StatusOK, "settings.account", data)
    292 	}
    293 
    294 	if data.Website != "" && !govalidator.IsURL(data.Website) {
    295 		data.ErrorWebsite = "invalid website"
    296 		return c.Render(http.StatusOK, "settings.account", data)
    297 	}
    298 
    299 	authUser.Website = data.Website
    300 	authUser.Email = data.Email
    301 	authUser.LastSeenPublic = data.LastSeenPublic
    302 	authUser.TerminateAllSessionsOnLogout = data.TerminateAllSessionsOnLogout
    303 	authUser.DoSave(db)
    304 
    305 	return c.Render(http.StatusOK, "flash", FlashResponse{Message: "Profile changed successfully", Redirect: "/settings/account"})
    306 }
    307 
    308 func changeAvatarForm(c echo.Context, data settingsAccountData) error {
    309 	authUser := c.Get("authUser").(*database.User)
    310 	db := c.Get("database").(*database.DkfDB)
    311 	if !authUser.CanUpload() {
    312 		data.ErrorAvatar = hutils.AccountTooYoungErr.Error()
    313 		return c.Render(http.StatusOK, "settings.account", data)
    314 	}
    315 	if err := c.Request().ParseMultipartForm(config.MaxAvatarFormSize); err != nil {
    316 		data.ErrorAvatar = err.Error()
    317 		return c.Render(http.StatusOK, "settings.account", data)
    318 	}
    319 	file, handler, err := c.Request().FormFile("avatar")
    320 	if err != nil {
    321 		data.ErrorAvatar = "Failed to get avatar: " + err.Error()
    322 		return c.Render(http.StatusOK, "settings.account", data)
    323 	}
    324 	defer file.Close()
    325 	if handler.Size > config.MaxAvatarSize {
    326 		data.ErrorAvatar = fmt.Sprintf("The maximum file size for avatars is %s", humanize.Bytes(config.MaxAvatarSize))
    327 		return c.Render(http.StatusOK, "settings.account", data)
    328 	}
    329 	fileBytes, err := io.ReadAll(file)
    330 	if err != nil {
    331 		data.ErrorAvatar = err.Error()
    332 		return c.Render(http.StatusOK, "settings.account", data)
    333 	}
    334 
    335 	filetype := http.DetectContentType(fileBytes)
    336 	if !utils.InArr(filetype, []string{"image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp"}) {
    337 		data.ErrorAvatar = "The provided file format is not allowed. Please upload a JPEG, PNG, WEBP, BMP or GIF image"
    338 		return c.Render(http.StatusOK, "settings.account", data)
    339 	}
    340 
    341 	// Validate image type and determine extension
    342 	if handler.Header.Get("Content-Type") != filetype {
    343 		data.ErrorAvatar = "Content-Type does not match mimetype"
    344 		return c.Render(http.StatusOK, "settings.account", data)
    345 	}
    346 
    347 	im, _, err := image.DecodeConfig(bytes.NewReader(fileBytes))
    348 	if err != nil {
    349 		data.ErrorAvatar = err.Error()
    350 		return c.Render(http.StatusOK, "settings.account", data)
    351 	}
    352 	if im.Width > 120 || im.Height > 120 {
    353 		data.ErrorAvatar = "The maximum dimensions for avatars are: 120x120 pixels"
    354 		return c.Render(http.StatusOK, "settings.account", data)
    355 	}
    356 
    357 	if filetype == "image/jpeg" {
    358 		fileBytes, err = utils.ReencodeJpg(fileBytes)
    359 	} else if filetype == "image/png" {
    360 		fileBytes, err = utils.ReencodePng(fileBytes)
    361 	}
    362 	if err != nil {
    363 		data.Error = err.Error()
    364 		return c.Render(http.StatusOK, "settings.account", data)
    365 	}
    366 
    367 	authUser.SetAvatar(fileBytes)
    368 	authUser.DoSave(db)
    369 	return c.Render(http.StatusOK, "flash", FlashResponse{Message: "Avatar changed successfully", Redirect: "/settings/account"})
    370 }
    371 
    372 func SettingsChatPMHandler(c echo.Context) error {
    373 	authUser := c.Get("authUser").(*database.User)
    374 	db := c.Get("database").(*database.DkfDB)
    375 	var data settingsChatPMData
    376 	data.ActiveTab = "chat"
    377 	data.PmMode = authUser.PmMode
    378 	data.BlockNewUsersPm = authUser.BlockNewUsersPm
    379 	data.WhitelistedUsers, _ = db.GetPmWhitelistedUsers(authUser.ID)
    380 	data.BlacklistedUsers, _ = db.GetPmBlacklistedUsers(authUser.ID)
    381 
    382 	if c.Request().Method == http.MethodGet {
    383 		return c.Render(http.StatusOK, "settings.chat-pm", data)
    384 	}
    385 
    386 	// POST
    387 	formName := c.Request().PostFormValue("formName")
    388 
    389 	if formName == "addWhitelist" {
    390 		data.AddWhitelist = database.Username(strings.TrimSpace(c.Request().PostFormValue("username")))
    391 		user, err := db.GetUserByUsername(data.AddWhitelist)
    392 		if err != nil {
    393 			data.Error = "username not found"
    394 			return c.Render(http.StatusOK, "settings.chat-pm", data)
    395 		}
    396 		db.AddWhitelistedUser(authUser.ID, user.ID)
    397 		return c.Redirect(http.StatusFound, "/settings/chat/pm")
    398 
    399 	} else if formName == "rmWhitelist" {
    400 		userID := dutils.DoParseUserID(c.Request().PostFormValue("userID"))
    401 		db.RmWhitelistedUser(authUser.ID, userID)
    402 		return c.Redirect(http.StatusFound, "/settings/chat/pm")
    403 
    404 	} else if formName == "addBlacklist" {
    405 		data.AddBlacklist = database.Username(strings.TrimSpace(c.Request().PostFormValue("username")))
    406 		user, err := db.GetUserByUsername(data.AddBlacklist)
    407 		if err != nil {
    408 			data.Error = "username not found"
    409 			return c.Render(http.StatusOK, "settings.chat-pm", data)
    410 		}
    411 		db.AddBlacklistedUser(authUser.ID, user.ID)
    412 		return c.Redirect(http.StatusFound, "/settings/chat/pm")
    413 
    414 	} else if formName == "rmBlacklist" {
    415 		userID := dutils.DoParseUserID(c.Request().PostFormValue("userID"))
    416 		db.RmBlacklistedUser(authUser.ID, userID)
    417 		return c.Redirect(http.StatusFound, "/settings/chat/pm")
    418 	}
    419 
    420 	data.PmMode = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("pm_mode")), 0, 1)
    421 	authUser.BlockNewUsersPm = utils.DoParseBool(c.Request().PostFormValue("block_new_users_pm"))
    422 	authUser.PmMode = data.PmMode
    423 	authUser.DoSave(db)
    424 	return c.Redirect(http.StatusFound, "/settings/chat/pm")
    425 }
    426 
    427 func SettingsChatIgnoreHandler(c echo.Context) error {
    428 	authUser := c.Get("authUser").(*database.User)
    429 	db := c.Get("database").(*database.DkfDB)
    430 	var data settingsChatIgnoreData
    431 	data.ActiveTab = "chat"
    432 	data.PmMode = authUser.PmMode
    433 	data.IgnoredUsers, _ = db.GetIgnoredUsers(authUser.ID)
    434 
    435 	if c.Request().Method == http.MethodGet {
    436 		return c.Render(http.StatusOK, "settings.chat-ignore", data)
    437 	}
    438 
    439 	// POST
    440 	formName := c.Request().PostFormValue("formName")
    441 
    442 	if formName == "addIgnored" {
    443 		data.AddIgnored = database.Username(strings.TrimSpace(c.Request().PostFormValue("username")))
    444 		user, err := db.GetUserByUsername(data.AddIgnored)
    445 		if err != nil {
    446 			data.Error = "username not found"
    447 			return c.Render(http.StatusOK, "settings.chat-ignore", data)
    448 		}
    449 		db.IgnoreUser(authUser.ID, user.ID)
    450 		return c.Redirect(http.StatusFound, "/settings/chat/ignore")
    451 
    452 	} else if formName == "rmIgnored" {
    453 		userID := dutils.DoParseUserID(c.Request().PostFormValue("userID"))
    454 		db.UnIgnoreUser(authUser.ID, userID)
    455 		return c.Redirect(http.StatusFound, "/settings/chat/ignore")
    456 	}
    457 
    458 	data.PmMode = utils.Clamp(utils.DoParseInt64(c.Request().PostFormValue("pm_mode")), 0, 1)
    459 	authUser.PmMode = data.PmMode
    460 	authUser.DoSave(db)
    461 	return c.Redirect(http.StatusFound, "/settings/chat/ignore")
    462 }
    463 
    464 func SettingsChatSnippetsHandler(c echo.Context) error {
    465 	authUser := c.Get("authUser").(*database.User)
    466 	db := c.Get("database").(*database.DkfDB)
    467 	var data settingsChatSnippetsData
    468 	data.ActiveTab = "snippets"
    469 	data.Snippets, _ = db.GetUserSnippets(authUser.ID)
    470 
    471 	if c.Request().Method == http.MethodGet {
    472 		return c.Render(http.StatusOK, "settings.chat-snippets", data)
    473 	}
    474 
    475 	// POST
    476 	formName := c.Request().PostFormValue("formName")
    477 
    478 	if formName == "addSnippet" {
    479 		data.Name = strings.TrimSpace(c.Request().PostFormValue("name"))
    480 		data.Text = strings.TrimSpace(c.Request().PostFormValue("text"))
    481 		if len(data.Snippets) >= 20 {
    482 			data.Error = "snippets limit reached"
    483 			return c.Render(http.StatusOK, "settings.chat-snippets", data)
    484 		}
    485 		if !govalidator.Matches(data.Name, `^\w{1,20}$`) {
    486 			data.Error = "name must match : ^\\w{1,20}$"
    487 			return c.Render(http.StatusOK, "settings.chat-snippets", data)
    488 		}
    489 		if !govalidator.StringLength(data.Name, "1", "20") {
    490 			data.Error = "name must be 1-20 characters"
    491 			return c.Render(http.StatusOK, "settings.chat-snippets", data)
    492 		}
    493 		if !govalidator.StringLength(data.Text, "1", "1000") {
    494 			data.Error = "text must be 1-1000 characters"
    495 			return c.Render(http.StatusOK, "settings.chat-snippets", data)
    496 		}
    497 		if _, err := db.CreateSnippet(authUser.ID, data.Name, data.Text); err != nil {
    498 			data.Error = err.Error()
    499 			return c.Render(http.StatusOK, "settings.chat-snippets", data)
    500 		}
    501 		return c.Redirect(http.StatusFound, "/settings/chat/snippets")
    502 
    503 	} else if formName == "rmSnippet" {
    504 		snippetName := c.Request().PostFormValue("snippetName")
    505 		db.DeleteSnippet(authUser.ID, snippetName)
    506 		return c.Redirect(http.StatusFound, "/settings/chat/snippets")
    507 	}
    508 
    509 	return c.Redirect(http.StatusFound, "/settings/chat/snippets")
    510 }
    511 
    512 func SettingsPublicNotesHandler(c echo.Context) error {
    513 	authUser := c.Get("authUser").(*database.User)
    514 	db := c.Get("database").(*database.DkfDB)
    515 	if !authUser.CanUseForumFn() {
    516 		return c.Render(http.StatusOK, "flash", FlashResponse{Message: hutils.AccountTooYoungErr.Error(), Redirect: "/settings/account", Type: "alert-danger"})
    517 	}
    518 	var data settingsPublicNotesData
    519 	data.ActiveTab = "notes"
    520 	data.Notes, _ = db.GetUserPublicNotes(authUser.ID)
    521 
    522 	if c.Request().Method == http.MethodPost {
    523 		notes := c.Request().PostFormValue("public_notes")
    524 		if err := db.SetUserPublicNotes(authUser.ID, notes); err != nil {
    525 			data.Error = err.Error()
    526 			return c.Render(http.StatusOK, "settings.public-notes", data)
    527 		}
    528 		return c.Redirect(http.StatusFound, "/settings/public-notes")
    529 	}
    530 
    531 	return c.Render(http.StatusOK, "settings.public-notes", data)
    532 }
    533 
    534 func SettingsPrivateNotesHandler(c echo.Context) error {
    535 	authUser := c.Get("authUser").(*database.User)
    536 	db := c.Get("database").(*database.DkfDB)
    537 	if !authUser.CanUseForumFn() {
    538 		return c.Render(http.StatusOK, "flash", FlashResponse{Message: hutils.AccountTooYoungErr.Error(), Redirect: "/settings/account", Type: "alert-danger"})
    539 	}
    540 	var data settingsPrivateNotesData
    541 	data.ActiveTab = "notes"
    542 	if !authUser.IsUnderDuress {
    543 		data.Notes, _ = db.GetUserPrivateNotes(authUser.ID)
    544 	}
    545 
    546 	if c.Request().Method == http.MethodPost {
    547 		notes := c.Request().PostFormValue("private_notes")
    548 		if err := db.SetUserPrivateNotes(authUser.ID, notes); err != nil {
    549 			data.Error = err.Error()
    550 			return c.Render(http.StatusOK, "settings.private-notes", data)
    551 		}
    552 		return c.Redirect(http.StatusFound, "/settings/private-notes")
    553 	}
    554 
    555 	return c.Render(http.StatusOK, "settings.private-notes", data)
    556 }
    557 
    558 func SettingsSessionsHandler(c echo.Context) error {
    559 	authUser := c.Get("authUser").(*database.User)
    560 	db := c.Get("database").(*database.DkfDB)
    561 	var data settingsSessionsData
    562 	data.ActiveTab = "sessions"
    563 	sessions := db.GetActiveUserSessions(authUser.ID)
    564 	authCookie, _ := c.Cookie(hutils.AuthCookieName)
    565 	for _, session := range sessions {
    566 		s := WrapperSession{Session: session}
    567 		if authCookie.Value == s.Token {
    568 			s.CurrentSession = true
    569 		}
    570 		data.Sessions = append(data.Sessions, s)
    571 	}
    572 
    573 	if c.Request().Method == http.MethodPost {
    574 		formName := c.Request().PostFormValue("formName")
    575 		if formName == "revoke_all_other_sessions" {
    576 			_ = db.DeleteUserOtherSessions(authUser.ID, authCookie.Value)
    577 		} else {
    578 			sessionToken := c.Request().PostFormValue("sessionToken")
    579 			_ = db.DeleteUserSessionByToken(authUser.ID, sessionToken)
    580 		}
    581 		return c.Redirect(http.StatusFound, "/settings/sessions")
    582 	}
    583 
    584 	return c.Render(http.StatusOK, "settings.sessions", data)
    585 }
    586 
    587 func SettingsAPIHandler(c echo.Context) error {
    588 	authUser := c.Get("authUser").(*database.User)
    589 	db := c.Get("database").(*database.DkfDB)
    590 	var data settingsAPIData
    591 	data.ActiveTab = "api"
    592 	data.APIKey = authUser.ApiKey
    593 	if c.Request().Method == http.MethodPost {
    594 		formName := c.Request().PostFormValue("formName")
    595 		btnSubmit := c.Request().PostFormValue("btn_submit")
    596 		if btnSubmit == "Cancel" {
    597 			return c.Redirect(http.StatusFound, "/settings/api")
    598 		}
    599 		if formName == "confirm" {
    600 			token := utils.GenerateToken16()
    601 			authUser.SetApiKey(db, token)
    602 			return c.Redirect(http.StatusFound, "/settings/api")
    603 		}
    604 		data.NeedConfirm = true
    605 	}
    606 	return c.Render(http.StatusOK, "settings.api", data)
    607 }
    608 
    609 func SettingsPasswordHandler(c echo.Context) error {
    610 	var data settingsPasswordData
    611 	data.ActiveTab = "password"
    612 
    613 	if c.Request().Method == http.MethodGet {
    614 		return c.Render(http.StatusOK, "settings.password", data)
    615 	}
    616 
    617 	// POST
    618 	formName := c.FormValue("formName")
    619 	switch formName {
    620 	case "changePassword":
    621 		return changePasswordForm(c, data)
    622 	case "changeDuressPassword":
    623 		return changeDuressPasswordForm(c, data)
    624 	default:
    625 		return c.Render(http.StatusOK, "settings.password", data)
    626 	}
    627 }
    628 
    629 func changePasswordForm(c echo.Context, data settingsPasswordData) error {
    630 	authUser := c.Get("authUser").(*database.User)
    631 	db := c.Get("database").(*database.DkfDB)
    632 	oldPassword := c.Request().PostFormValue("oldPassword")
    633 	newPassword := c.Request().PostFormValue("newPassword")
    634 	rePassword := c.Request().PostFormValue("rePassword")
    635 	data.OldPassword = oldPassword
    636 	data.NewPassword = newPassword
    637 	data.RePassword = rePassword
    638 
    639 	if len(oldPassword) == 0 {
    640 		data.ErrorOldPassword = "This field is required"
    641 		return c.Render(http.StatusOK, "settings.password", data)
    642 	}
    643 
    644 	if len(newPassword) > 0 || len(rePassword) > 0 {
    645 		hashedPassword, err := database.NewPasswordValidator(db, newPassword).CompareWith(rePassword).Hash()
    646 		if err != nil {
    647 			data.ErrorNewPassword = err.Error()
    648 			return c.Render(http.StatusOK, "settings.password", data)
    649 		}
    650 
    651 		if !authUser.CheckPassword(db, oldPassword) {
    652 			data.ErrorOldPassword = "Invalid password"
    653 			return c.Render(http.StatusOK, "settings.password", data)
    654 		}
    655 
    656 		if err := authUser.ChangePassword(db, hashedPassword); err != nil {
    657 			logrus.Error(err)
    658 		}
    659 		c.SetCookie(hutils.DeleteCookie(hutils.AuthCookieName))
    660 		db.CreateSecurityLog(authUser.ID, database.ChangePasswordSecurityLog)
    661 		return c.Render(http.StatusFound, "flash", FlashResponse{Message: "Password changed successfully", Redirect: "/login"})
    662 	}
    663 
    664 	return c.Redirect(http.StatusFound, "/settings/password")
    665 }
    666 
    667 func changeDuressPasswordForm(c echo.Context, data settingsPasswordData) error {
    668 	authUser := c.Get("authUser").(*database.User)
    669 	db := c.Get("database").(*database.DkfDB)
    670 	oldDuressPassword := c.Request().PostFormValue("oldDuressPassword")
    671 	newDuressPassword := c.Request().PostFormValue("newDuressPassword")
    672 	reDuressPassword := c.Request().PostFormValue("reDuressPassword")
    673 	data.OldDuressPassword = oldDuressPassword
    674 	data.NewDuressPassword = newDuressPassword
    675 	data.ReDuressPassword = reDuressPassword
    676 
    677 	if len(oldDuressPassword) == 0 {
    678 		data.ErrorOldDuressPassword = "This field is required"
    679 		return c.Render(http.StatusOK, "settings.password", data)
    680 	}
    681 
    682 	if len(newDuressPassword) > 0 || len(reDuressPassword) > 0 {
    683 		hashedPassword, err := database.NewPasswordValidator(db, newDuressPassword).CompareWith(reDuressPassword).Hash()
    684 		if err != nil {
    685 			data.ErrorNewDuressPassword = err.Error()
    686 			return c.Render(http.StatusOK, "settings.password", data)
    687 		}
    688 
    689 		if !authUser.CheckPassword(db, oldDuressPassword) {
    690 			data.ErrorOldDuressPassword = "Invalid password"
    691 			return c.Render(http.StatusOK, "settings.password", data)
    692 		}
    693 
    694 		if err := authUser.ChangeDuressPassword(db, hashedPassword); err != nil {
    695 			logrus.Error(err)
    696 		}
    697 		c.SetCookie(hutils.DeleteCookie(hutils.AuthCookieName))
    698 		db.CreateSecurityLog(authUser.ID, database.ChangeDuressPasswordSecurityLog)
    699 		return c.Render(http.StatusFound, "flash", FlashResponse{Message: "Password changed successfully", Redirect: "/login"})
    700 	}
    701 
    702 	return c.Redirect(http.StatusFound, "/settings/password")
    703 }
    704 
    705 func SettingsUploadsHandler(c echo.Context) error {
    706 	authUser := c.Get("authUser").(*database.User)
    707 	db := c.Get("database").(*database.DkfDB)
    708 	var data settingsUploadsData
    709 	data.ActiveTab = "uploads"
    710 	data.Files, _ = db.GetUserUploads(authUser.ID)
    711 	for _, f := range data.Files {
    712 		data.TotalSize += f.FileSize
    713 	}
    714 
    715 	if c.Request().Method == http.MethodGet {
    716 		return c.Render(http.StatusOK, "settings.uploads", data)
    717 	}
    718 
    719 	// POST
    720 	formName := c.FormValue("formName")
    721 	if formName == "deleteUpload" {
    722 		fileName := c.Request().PostFormValue("file_name")
    723 		file, err := db.GetUploadByFileName(fileName)
    724 		if err != nil {
    725 			return c.Redirect(http.StatusFound, "/")
    726 		}
    727 		if authUser.ID != file.UserID {
    728 			return c.Redirect(http.StatusFound, "/")
    729 		}
    730 		if err := file.Delete(db); err != nil {
    731 			logrus.Error(err)
    732 		}
    733 		return c.Redirect(http.StatusFound, "/settings/uploads")
    734 	}
    735 	return c.Render(http.StatusOK, "settings.uploads", data)
    736 }
    737 
    738 func SettingsInboxHandler(c echo.Context) error {
    739 	authCookie, _ := c.Cookie(hutils.AuthCookieName)
    740 	authUser := c.Get("authUser").(*database.User)
    741 	db := c.Get("database").(*database.DkfDB)
    742 	var data settingsInboxData
    743 	data.ActiveTab = "inbox"
    744 	// Do not fetch inboxes & notifications if logged in under duress
    745 	if !authUser.IsUnderDuress {
    746 		global.DeleteUserNotificationCount(authUser.ID, authCookie.Value)
    747 		data.ChatMessages, _ = db.GetUserChatInboxMessages(authUser.ID)
    748 		data.Notifications, _ = db.GetUserNotifications(authUser.ID)
    749 		data.SessionNotifications, _ = db.GetUserSessionNotifications(authCookie.Value)
    750 	}
    751 	for _, m := range data.ChatMessages {
    752 		data.Notifs = append(data.Notifs, InboxTmp{IsNotif: false, ChatInboxMessage: m})
    753 	}
    754 	for _, m := range data.Notifications {
    755 		data.Notifs = append(data.Notifs, InboxTmp{IsNotif: true, Notification: m})
    756 	}
    757 	for _, m := range data.SessionNotifications {
    758 		data.Notifs = append(data.Notifs, InboxTmp{IsNotif: true, SessionNotification: m})
    759 	}
    760 	sort.Slice(data.Notifs, func(i, j int) bool {
    761 		a := data.Notifs[i]
    762 		b := data.Notifs[j]
    763 		var tsa time.Time
    764 		var tsb time.Time
    765 		if a.Notification.ID != 0 {
    766 			tsa = a.Notification.CreatedAt
    767 		} else if a.SessionNotification.ID != 0 {
    768 			tsa = a.SessionNotification.CreatedAt
    769 		} else {
    770 			tsa = a.ChatInboxMessage.CreatedAt
    771 		}
    772 		if b.Notification.ID != 0 {
    773 			tsb = b.Notification.CreatedAt
    774 		} else if b.SessionNotification.ID != 0 {
    775 			tsb = b.SessionNotification.CreatedAt
    776 		} else {
    777 			tsb = b.ChatInboxMessage.CreatedAt
    778 		}
    779 		return tsa.After(tsb)
    780 	})
    781 	return c.Render(http.StatusOK, "settings.inbox", data)
    782 }
    783 
    784 func SettingsInboxSentHandler(c echo.Context) error {
    785 	authUser := c.Get("authUser").(*database.User)
    786 	db := c.Get("database").(*database.DkfDB)
    787 	var data settingsInboxSentData
    788 	data.ActiveTab = "inbox"
    789 	// Do not fetch inboxes & notifications if logged in under duress
    790 	if !authUser.IsUnderDuress {
    791 		data.ChatInboxSent, _ = db.GetUserChatInboxMessagesSent(authUser.ID)
    792 	}
    793 	return c.Render(http.StatusOK, "settings.inbox-sent", data)
    794 }
    795 
    796 func AddPGPHandler(c echo.Context) error {
    797 	authUser := c.Get("authUser").(*database.User)
    798 	db := c.Get("database").(*database.DkfDB)
    799 	var data addPGPData
    800 	data.PGPPublicKey = authUser.GPGPublicKey
    801 	if c.Request().Method == http.MethodPost {
    802 		formName := c.Request().PostFormValue("formName")
    803 		if formName == "pgp_step1" {
    804 
    805 			data.PGPPublicKey = c.Request().PostFormValue("pgp_public_key")
    806 			data.GpgMode = utils.DoParseBool(c.Request().PostFormValue("gpg_mode"))
    807 
    808 			if data.GpgMode {
    809 				data.ToBeSignedMessage = generatePgpToBeSignedTokenMessage(authUser.ID, data.PGPPublicKey)
    810 				return c.Render(http.StatusOK, "pgp_code", data)
    811 
    812 			} else {
    813 				msg, err := generatePgpEncryptedTokenMessage(authUser.ID, data.PGPPublicKey)
    814 				if err != nil {
    815 					data.ErrorPGPPublicKey = err.Error()
    816 					return c.Render(http.StatusOK, "pgp", data)
    817 				}
    818 				data.EncryptedMessage = msg
    819 				return c.Render(http.StatusOK, "pgp_code", data)
    820 			}
    821 
    822 		} else if formName == "pgp_step2" {
    823 			token, found := pgpTokenCache.Get(authUser.ID)
    824 			if !found {
    825 				return c.Redirect(http.StatusFound, "/settings/pgp")
    826 			}
    827 
    828 			data.PGPPublicKey = c.Request().PostFormValue("pgp_public_key")
    829 			data.GpgMode = utils.DoParseBool(c.Request().PostFormValue("gpg_mode"))
    830 			if data.GpgMode {
    831 				data.ToBeSignedMessage = c.Request().PostFormValue("to_be_signed_message")
    832 				data.SignedMessage = c.Request().PostFormValue("signed_message")
    833 				if !utils.PgpCheckSignMessage(token.PKey, token.Value, data.SignedMessage) {
    834 					data.ErrorSignedMessage = "invalid signature"
    835 					return c.Render(http.StatusOK, "pgp_code", data)
    836 				}
    837 
    838 			} else {
    839 				data.EncryptedMessage = c.Request().PostFormValue("encrypted_message")
    840 				data.Code = c.Request().PostFormValue("pgp_code")
    841 				if data.Code != token.Value {
    842 					data.ErrorCode = "invalid code"
    843 					return c.Render(http.StatusOK, "pgp_code", data)
    844 				}
    845 			}
    846 
    847 			pgpTokenCache.Delete(authUser.ID)
    848 			authUser.GPGPublicKey = token.PKey
    849 			authUser.DoSave(db)
    850 			return c.Redirect(http.StatusFound, "/settings/pgp")
    851 		}
    852 	}
    853 	return c.Render(http.StatusOK, "pgp", data)
    854 }
    855 
    856 func SettingsPGPHandler(c echo.Context) error {
    857 	authUser := c.Get("authUser").(*database.User)
    858 	var data settingsPGPData
    859 	data.ActiveTab = "pgp"
    860 
    861 	if authUser.GPGPublicKey != "" {
    862 		if e := utils.GetEntityFromPKey(authUser.GPGPublicKey); e != nil {
    863 			data.PGPPublicKeyID = e.PrimaryKey.KeyIdString()
    864 		}
    865 	}
    866 
    867 	return c.Render(http.StatusOK, "settings.pgp", data)
    868 }
    869 
    870 func SettingsAgeHandler(c echo.Context) error {
    871 	authUser := c.Get("authUser").(*database.User)
    872 	var data settingsAgeData
    873 	data.ActiveTab = "age"
    874 	data.AgePublicKey = authUser.AgePublicKey
    875 	return c.Render(http.StatusOK, "settings.age", data)
    876 }
    877 
    878 func AddAgeHandler(c echo.Context) error {
    879 	authUser := c.Get("authUser").(*database.User)
    880 	db := c.Get("database").(*database.DkfDB)
    881 	var data addAgeData
    882 	data.AgePublicKey = authUser.AgePublicKey
    883 	if c.Request().Method == http.MethodPost {
    884 		formName := c.Request().PostFormValue("formName")
    885 		if formName == "age_step1" {
    886 			data.AgePublicKey = c.Request().PostFormValue("age_public_key")
    887 			msg, err := generateAgeEncryptedTokenMessage(authUser.ID, data.AgePublicKey)
    888 			if err != nil {
    889 				data.ErrorAgePublicKey = err.Error()
    890 				return c.Render(http.StatusOK, "age", data)
    891 			}
    892 			data.EncryptedMessage = msg
    893 			return c.Render(http.StatusOK, "age_code", data)
    894 
    895 		} else if formName == "age_step2" {
    896 			token, found := ageTokenCache.Get(authUser.ID)
    897 			if !found {
    898 				return c.Redirect(http.StatusFound, "/settings/age")
    899 			}
    900 			data.AgePublicKey = token.PKey
    901 			data.EncryptedMessage = c.Request().PostFormValue("encrypted_message")
    902 			data.Code = c.Request().PostFormValue("age_code")
    903 			if data.Code != token.Value {
    904 				data.ErrorCode = "invalid code"
    905 				return c.Render(http.StatusOK, "age_code", data)
    906 			}
    907 			ageTokenCache.Delete(authUser.ID)
    908 			authUser.SetAgePublicKey(db, token.PKey)
    909 			return c.Redirect(http.StatusFound, "/settings/age")
    910 		}
    911 	}
    912 	return c.Render(http.StatusOK, "age", data)
    913 }
    914 
    915 func generateAgeEncryptedTokenMessage(userID database.UserID, pkey string) (string, error) {
    916 	token := utils.GenerateToken10()
    917 	ageTokenCache.SetD(userID, ValueTokenCache{Value: token, PKey: pkey})
    918 
    919 	recipient, err := age.ParseX25519Recipient(pkey)
    920 	if err != nil {
    921 		logrus.Error(err)
    922 		return "", errors.New("invalid public key")
    923 	}
    924 	out := &bytes.Buffer{}
    925 	aw := armor1.NewWriter(out)
    926 	w, err := age.Encrypt(aw, recipient)
    927 	msg := generateTokenMsg(token)
    928 	if _, err := io.WriteString(w, msg); err != nil {
    929 		logrus.Error(err)
    930 		w.Close()
    931 		aw.Close()
    932 		return "", err
    933 	}
    934 	w.Close()
    935 	aw.Close()
    936 
    937 	return out.String(), nil
    938 }
    939 
    940 // SettingsWebsiteHandler ...
    941 func SettingsWebsiteHandler(c echo.Context) error {
    942 	authUser := c.Get("authUser").(*database.User)
    943 	db := c.Get("database").(*database.DkfDB)
    944 	var data settingsWebsiteData
    945 	data.ActiveTab = "website"
    946 	settings := db.GetSettings()
    947 	data.SignupEnabled = settings.SignupEnabled
    948 	data.ForumEnabled = settings.ForumEnabled
    949 	data.SilentSelfKick = settings.SilentSelfKick
    950 	if c.Request().Method == http.MethodPost {
    951 		settings.SignupEnabled = utils.DoParseBool(c.Request().PostFormValue("signupEnabled"))
    952 		settings.ForumEnabled = utils.DoParseBool(c.Request().PostFormValue("forumEnabled"))
    953 		settings.SilentSelfKick = utils.DoParseBool(c.Request().PostFormValue("silentSelfKick"))
    954 		settings.DoSave(db)
    955 		config.SignupEnabled.Store(settings.SignupEnabled)
    956 		config.ForumEnabled.Store(settings.ForumEnabled)
    957 		config.SilentSelfKick.Store(settings.SilentSelfKick)
    958 		db.NewAudit(*authUser, fmt.Sprintf("website settings, signup: %t, forum: %t, sk: %t",
    959 			settings.SignupEnabled, settings.ForumEnabled, settings.SilentSelfKick))
    960 		return c.Redirect(http.StatusFound, "/settings/website")
    961 	}
    962 
    963 	return c.Render(http.StatusOK, "settings.website", data)
    964 }
    965 
    966 // SettingsInvitationsHandler ...
    967 func SettingsInvitationsHandler(c echo.Context) error {
    968 	authUser := c.Get("authUser").(*database.User)
    969 	db := c.Get("database").(*database.DkfDB)
    970 	var data settingsInvitationsData
    971 	data.ActiveTab = "invitations"
    972 	data.DkfOnion = config.DkfOnion
    973 
    974 	if c.Request().Method == http.MethodPost {
    975 		formName := c.Request().PostFormValue("formName")
    976 		if formName == "createInvitation" {
    977 			utils.LogErr2(db.CreateInvitation(authUser.ID))
    978 		} else if formName == "deleteAll" {
    979 			utils.LogErr(db.DeleteAllUserUnusedInvitations(authUser.ID))
    980 		}
    981 		return c.Redirect(http.StatusFound, "/settings/invitations")
    982 	}
    983 
    984 	data.Invitations, _ = db.GetUserUnusedInvitations(authUser.ID)
    985 	return c.Render(http.StatusOK, "settings.invitations", data)
    986 }
    987 
    988 func SettingsSecretPhraseHandler(c echo.Context) error {
    989 	authUser := c.Get("authUser").(*database.User)
    990 	db := c.Get("database").(*database.DkfDB)
    991 	var data settingsSecretPhraseData
    992 	data.ActiveTab = "secretPhrase"
    993 	data.SecretPhrase = string(authUser.SecretPhrase)
    994 
    995 	if c.Request().Method == http.MethodGet {
    996 		return c.Render(http.StatusOK, "settings.secret-phrase", data)
    997 	}
    998 
    999 	// POST
   1000 	currentPassword := c.Request().PostFormValue("currentPassword")
   1001 	secretPhrase := c.Request().PostFormValue("secretPhrase")
   1002 	data.CurrentPassword = currentPassword
   1003 	data.SecretPhrase = secretPhrase
   1004 
   1005 	if len(currentPassword) == 0 {
   1006 		data.ErrorCurrentPassword = "This field is required"
   1007 		return c.Render(http.StatusOK, "settings.secret-phrase", data)
   1008 	}
   1009 
   1010 	if !govalidator.RuneLength(secretPhrase, "3", "50") {
   1011 		data.ErrorSecretPhrase = "secret phrase must have between 3 and 50 characters"
   1012 		return c.Render(http.StatusOK, "settings.secret-phrase", data)
   1013 	}
   1014 
   1015 	if !authUser.CheckPassword(db, currentPassword) {
   1016 		data.ErrorCurrentPassword = "Invalid password"
   1017 		return c.Render(http.StatusOK, "settings.secret-phrase", data)
   1018 	}
   1019 
   1020 	authUser.SecretPhrase = database.EncryptedString(secretPhrase)
   1021 	authUser.DoSave(db)
   1022 
   1023 	db.CreateSecurityLog(authUser.ID, database.ChangeSecretPhraseSecurityLog)
   1024 	return c.Render(http.StatusFound, "flash", FlashResponse{Message: "Secret phrase changed successfully", Redirect: "/"})
   1025 }