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 }