From 226f218f80b25a2843bbe35b4cdd0f26d1d9602a Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 21 Sep 2020 09:40:35 +0200 Subject: [PATCH] Backend: Count login attempts and localize error messages Signed-off-by: Michael Mayer --- assets/locales/de/default.mo | Bin 4601 -> 4707 bytes assets/locales/de/default.po | 130 ++++++++++++++++-------------- assets/locales/en/default.mo | Bin 4274 -> 4372 bytes assets/locales/en/default.po | 134 +++++++++++++++--------------- assets/locales/messages.pot | 152 ++++++++++++++++++----------------- internal/api/session.go | 9 ++- internal/api/session_test.go | 3 +- internal/entity/person.go | 16 +++- internal/i18n/messages.go | 4 + 9 files changed, 246 insertions(+), 202 deletions(-) diff --git a/assets/locales/de/default.mo b/assets/locales/de/default.mo index dbe149848a9b6ca25c083cb848a874f16f487319..0051148cc9ad9b0e2466739f36ad39f8b52133dd 100644 GIT binary patch delta 1357 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 diff --git a/assets/locales/en/default.po b/assets/locales/en/default.po index 9f8a734ef..5cb57a68c 100644 --- a/assets/locales/en/default.po +++ b/assets/locales/en/default.po @@ -7,264 +7,272 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-07-14 19:44+0000\n" -"PO-Revision-Date: 2020-07-14 22:03+0200\n" +"POT-Creation-Date: 2020-09-21 07:26+0000\n" +"PO-Revision-Date: 2020-09-21 09:29+0200\n" +"Last-Translator: \n" "Language-Team: \n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.3.1\n" -"Last-Translator: \n" +"X-Generator: Poedit 2.4.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Language: en\n" -#: messages.go:69 +#: messages.go:71 msgid "Unexpected error, please try again" msgstr "Unexpected error, please try again" -#: messages.go:70 +#: messages.go:72 msgid "Invalid request" msgstr "Invalid request" -#: messages.go:71 +#: messages.go:73 msgid "Changes could not be saved" msgstr "Changes could not be saved" -#: messages.go:72 +#: messages.go:74 msgid "Could not be deleted" msgstr "Could not be deleted" -#: messages.go:73 +#: messages.go:75 #, c-format msgid "%s already exists" msgstr "%s already exists" -#: messages.go:74 messages.go:77 +#: messages.go:76 messages.go:79 msgid "Not found on server, deleted?" msgstr "Not found on server, deleted?" -#: messages.go:75 +#: messages.go:77 msgid "File not found" msgstr "File not found" -#: messages.go:76 +#: messages.go:78 msgid "Selection not found" msgstr "Selection not found" -#: messages.go:78 +#: messages.go:80 msgid "Account not found" msgstr "Account not found" -#: messages.go:79 +#: messages.go:81 msgid "User not found" msgstr "User not found" -#: messages.go:80 +#: messages.go:82 msgid "Label not found" msgstr "Label not found" -#: messages.go:81 +#: messages.go:83 msgid "Album not found" msgstr "Album not found" -#: messages.go:82 +#: messages.go:84 msgid "Not available in public mode" msgstr "Not available in public mode" -#: messages.go:83 +#: messages.go:85 msgid "not available in read-only mode" msgstr "not available in read-only mode" -#: messages.go:84 +#: messages.go:86 msgid "Please log in and try again" msgstr "Please log in and try again" -#: messages.go:85 +#: messages.go:87 msgid "Upload might be offensive" msgstr "Upload might be offensive" -#: messages.go:86 +#: messages.go:88 msgid "No items selected" msgstr "No items selected" -#: messages.go:87 +#: messages.go:89 msgid "Failed creating file, please check permissions" msgstr "Failed creating file, please check permissions" -#: messages.go:88 +#: messages.go:90 msgid "Failed creating folder, please check permissions" msgstr "Failed creating folder, please check permissions" -#: messages.go:89 +#: messages.go:91 msgid "Could not connect, please try again" msgstr "Could not connect, please try again" -#: messages.go:90 +#: messages.go:92 msgid "Invalid password, please try again" msgstr "Invalid password, please try again" -#: messages.go:91 +#: messages.go:93 msgid "Feature disabled" msgstr "Feature disabled" -#: messages.go:92 +#: messages.go:94 msgid "No labels selected" msgstr "No labels selected" -#: messages.go:93 +#: messages.go:95 msgid "No albums selected" msgstr "No albums selected" -#: messages.go:94 +#: messages.go:96 msgid "No files available for download" msgstr "No files available for download" -#: messages.go:95 +#: messages.go:97 msgid "Failed to create zip file" msgstr "Failed to create zip file" #: messages.go:98 +msgid "Invalid credentials" +msgstr "Invalid credentials" + +#: messages.go:99 +msgid "Invalid link" +msgstr "Invalid link" + +#: messages.go:102 msgid "Changes successfully saved" msgstr "Changes successfully saved" -#: messages.go:99 +#: messages.go:103 msgid "Album created" msgstr "Album created" -#: messages.go:100 +#: messages.go:104 msgid "Album saved" msgstr "Album saved" -#: messages.go:101 +#: messages.go:105 #, c-format msgid "Album %s deleted" msgstr "Album %s deleted" -#: messages.go:102 +#: messages.go:106 msgid "Album contents cloned" msgstr "Album contents cloned" -#: messages.go:103 +#: messages.go:107 msgid "File removed from stack" msgstr "File removed from stack" -#: messages.go:104 +#: messages.go:108 #, c-format msgid "Selection added to %s" msgstr "Selection added to %s" -#: messages.go:105 +#: messages.go:109 #, c-format msgid "One entry added to %s" msgstr "One entry added to %s" -#: messages.go:106 +#: messages.go:110 #, c-format msgid "%d entries added to %s" msgstr "%d entries added to %s" -#: messages.go:107 +#: messages.go:111 #, c-format msgid "One entry removed from %s" msgstr "One entry removed from %s" -#: messages.go:108 +#: messages.go:112 #, c-format msgid "%d entries removed from %s" msgstr "%d entries removed from %s" -#: messages.go:109 +#: messages.go:113 msgid "Account created" msgstr "Account created" -#: messages.go:110 +#: messages.go:114 msgid "Account saved" msgstr "Account saved" -#: messages.go:111 +#: messages.go:115 msgid "Account deleted" msgstr "Account deleted" -#: messages.go:112 +#: messages.go:116 msgid "Settings saved" msgstr "Settings saved" -#: messages.go:113 +#: messages.go:117 msgid "Password changed" msgstr "Password changed" -#: messages.go:114 +#: messages.go:118 #, c-format msgid "Import completed in %d s" msgstr "Import completed in %d s" -#: messages.go:115 +#: messages.go:119 msgid "Import canceled" msgstr "Import canceled" -#: messages.go:116 +#: messages.go:120 #, c-format msgid "Indexing completed in %d s" msgstr "Indexing completed in %d s" -#: messages.go:117 +#: messages.go:121 msgid "Indexing originals..." msgstr "Indexing originals..." -#: messages.go:118 +#: messages.go:122 #, c-format msgid "Indexing files in %s" msgstr "Indexing files in %s" -#: messages.go:119 +#: messages.go:123 msgid "Indexing canceled" msgstr "Indexing canceled" -#: messages.go:120 +#: messages.go:124 #, c-format msgid "Removed %d files and %d photos" msgstr "Removed %d files and %d photos" -#: messages.go:121 +#: messages.go:125 #, c-format msgid "Moving files from %s" msgstr "Moving files from %s" -#: messages.go:122 +#: messages.go:126 #, c-format msgid "Copying files from %s" msgstr "Copying files from %s" -#: messages.go:123 +#: messages.go:127 msgid "Labels deleted" msgstr "Labels deleted" -#: messages.go:124 +#: messages.go:128 msgid "Label saved" msgstr "Label saved" -#: messages.go:125 +#: messages.go:129 #, c-format msgid "%d files uploaded in %d s" msgstr "%d files uploaded in %d s" -#: messages.go:126 +#: messages.go:130 msgid "Selection archived" msgstr "Selection archived" -#: messages.go:127 +#: messages.go:131 msgid "Selection restored" msgstr "Selection restored" -#: messages.go:128 +#: messages.go:132 msgid "Selection marked as private" msgstr "Selection marked as private" -#: messages.go:129 +#: messages.go:133 msgid "Albums deleted" msgstr "Albums deleted" -#: messages.go:130 +#: messages.go:134 #, c-format msgid "Zip created in %d s" msgstr "Zip created in %d s" diff --git a/assets/locales/messages.pot b/assets/locales/messages.pot index 7c05b338a..8d95669cc 100644 --- a/assets/locales/messages.pot +++ b/assets/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-09-07 14:15+0000\n" +"POT-Creation-Date: 2020-09-21 07:30+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,253 +17,261 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: messages.go:69 +#: messages.go:71 msgid "Unexpected error, please try again" msgstr "" -#: messages.go:70 +#: messages.go:72 msgid "Invalid request" msgstr "" -#: messages.go:71 +#: messages.go:73 msgid "Changes could not be saved" msgstr "" -#: messages.go:72 +#: messages.go:74 msgid "Could not be deleted" msgstr "" -#: messages.go:73 +#: messages.go:75 #, c-format msgid "%s already exists" msgstr "" -#: messages.go:74 messages.go:77 +#: messages.go:76 messages.go:79 msgid "Not found on server, deleted?" msgstr "" -#: messages.go:75 +#: messages.go:77 msgid "File not found" msgstr "" -#: messages.go:76 +#: messages.go:78 msgid "Selection not found" msgstr "" -#: messages.go:78 +#: messages.go:80 msgid "Account not found" msgstr "" -#: messages.go:79 +#: messages.go:81 msgid "User not found" msgstr "" -#: messages.go:80 +#: messages.go:82 msgid "Label not found" msgstr "" -#: messages.go:81 +#: messages.go:83 msgid "Album not found" msgstr "" -#: messages.go:82 +#: messages.go:84 msgid "Not available in public mode" msgstr "" -#: messages.go:83 +#: messages.go:85 msgid "not available in read-only mode" msgstr "" -#: messages.go:84 +#: messages.go:86 msgid "Please log in and try again" msgstr "" -#: messages.go:85 +#: messages.go:87 msgid "Upload might be offensive" msgstr "" -#: messages.go:86 +#: messages.go:88 msgid "No items selected" msgstr "" -#: messages.go:87 +#: messages.go:89 msgid "Failed creating file, please check permissions" msgstr "" -#: messages.go:88 +#: messages.go:90 msgid "Failed creating folder, please check permissions" msgstr "" -#: messages.go:89 +#: messages.go:91 msgid "Could not connect, please try again" msgstr "" -#: messages.go:90 +#: messages.go:92 msgid "Invalid password, please try again" msgstr "" -#: messages.go:91 +#: messages.go:93 msgid "Feature disabled" msgstr "" -#: messages.go:92 +#: messages.go:94 msgid "No labels selected" msgstr "" -#: messages.go:93 +#: messages.go:95 msgid "No albums selected" msgstr "" -#: messages.go:94 +#: messages.go:96 msgid "No files available for download" msgstr "" -#: messages.go:95 +#: messages.go:97 msgid "Failed to create zip file" msgstr "" #: messages.go:98 -msgid "Changes successfully saved" +msgid "Invalid credentials" msgstr "" #: messages.go:99 -msgid "Album created" -msgstr "" - -#: messages.go:100 -msgid "Album saved" -msgstr "" - -#: messages.go:101 -#, c-format -msgid "Album %s deleted" +msgid "Invalid link" msgstr "" #: messages.go:102 -msgid "Album contents cloned" +msgid "Changes successfully saved" msgstr "" #: messages.go:103 -msgid "File removed from stack" +msgid "Album created" msgstr "" #: messages.go:104 -#, c-format -msgid "Selection added to %s" +msgid "Album saved" msgstr "" #: messages.go:105 #, c-format -msgid "One entry added to %s" +msgid "Album %s deleted" msgstr "" #: messages.go:106 -#, c-format -msgid "%d entries added to %s" +msgid "Album contents cloned" msgstr "" #: messages.go:107 -#, c-format -msgid "One entry removed from %s" +msgid "File removed from stack" msgstr "" #: messages.go:108 #, c-format -msgid "%d entries removed from %s" +msgid "Selection added to %s" msgstr "" #: messages.go:109 -msgid "Account created" +#, c-format +msgid "One entry added to %s" msgstr "" #: messages.go:110 -msgid "Account saved" +#, c-format +msgid "%d entries added to %s" msgstr "" #: messages.go:111 -msgid "Account deleted" +#, c-format +msgid "One entry removed from %s" msgstr "" #: messages.go:112 -msgid "Settings saved" +#, c-format +msgid "%d entries removed from %s" msgstr "" #: messages.go:113 -msgid "Password changed" +msgid "Account created" msgstr "" #: messages.go:114 -#, c-format -msgid "Import completed in %d s" +msgid "Account saved" msgstr "" #: messages.go:115 -msgid "Import canceled" +msgid "Account deleted" msgstr "" #: messages.go:116 -#, c-format -msgid "Indexing completed in %d s" +msgid "Settings saved" msgstr "" #: messages.go:117 -msgid "Indexing originals..." +msgid "Password changed" msgstr "" #: messages.go:118 #, c-format -msgid "Indexing files in %s" +msgid "Import completed in %d s" msgstr "" #: messages.go:119 -msgid "Indexing canceled" +msgid "Import canceled" msgstr "" #: messages.go:120 #, c-format -msgid "Removed %d files and %d photos" +msgid "Indexing completed in %d s" msgstr "" #: messages.go:121 -#, c-format -msgid "Moving files from %s" +msgid "Indexing originals..." msgstr "" #: messages.go:122 #, c-format -msgid "Copying files from %s" +msgid "Indexing files in %s" msgstr "" #: messages.go:123 -msgid "Labels deleted" +msgid "Indexing canceled" msgstr "" #: messages.go:124 -msgid "Label saved" +#, c-format +msgid "Removed %d files and %d photos" msgstr "" #: messages.go:125 #, c-format -msgid "%d files uploaded in %d s" +msgid "Moving files from %s" msgstr "" #: messages.go:126 -msgid "Selection archived" +#, c-format +msgid "Copying files from %s" msgstr "" #: messages.go:127 -msgid "Selection restored" +msgid "Labels deleted" msgstr "" #: messages.go:128 -msgid "Selection marked as private" +msgid "Label saved" msgstr "" #: messages.go:129 -msgid "Albums deleted" +#, c-format +msgid "%d files uploaded in %d s" msgstr "" #: messages.go:130 +msgid "Selection archived" +msgstr "" + +#: messages.go:131 +msgid "Selection restored" +msgstr "" + +#: messages.go:132 +msgid "Selection marked as private" +msgstr "" + +#: messages.go:133 +msgid "Albums deleted" +msgstr "" + +#: messages.go:134 #, c-format msgid "Zip created in %d s" msgstr "" diff --git a/internal/api/session.go b/internal/api/session.go index 79ea47fec..bcff11318 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -7,6 +7,7 @@ import ( "github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" + "github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/session" ) @@ -38,7 +39,7 @@ func CreateSession(router *gin.RouterGroup) { links := entity.FindValidLinks(f.Token, "") if len(links) == 0 { - c.AbortWithStatusJSON(400, gin.H{"error": "Invalid link"}) + c.AbortWithStatusJSON(400, gin.H{"error": i18n.Msg(i18n.ErrInvalidLink)}) } data.Tokens = []string{f.Token} @@ -56,18 +57,18 @@ func CreateSession(router *gin.RouterGroup) { user := entity.FindPersonByUserName(f.UserName) if user == nil { - c.AbortWithStatusJSON(400, gin.H{"error": "Invalid user name or password"}) + c.AbortWithStatusJSON(400, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) return } if user.InvalidPassword(f.Password) { - c.AbortWithStatusJSON(400, gin.H{"error": "Invalid user name or password"}) + c.AbortWithStatusJSON(400, gin.H{"error": i18n.Msg(i18n.ErrInvalidCredentials)}) return } data.User = *user } else { - c.AbortWithStatusJSON(400, gin.H{"error": "Password required, please try again"}) + c.AbortWithStatusJSON(400, gin.H{"error": i18n.Msg(i18n.ErrInvalidPassword)}) return } diff --git a/internal/api/session_test.go b/internal/api/session_test.go index e04e62fa9..5f824f388 100644 --- a/internal/api/session_test.go +++ b/internal/api/session_test.go @@ -1,6 +1,7 @@ package api import ( + "github.com/photoprism/photoprism/internal/i18n" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" "net/http" @@ -39,7 +40,7 @@ func TestCreateSession(t *testing.T) { CreateSession(router) r := PerformRequestWithBody(app, "POST", "/api/v1/session", `{"username": "admin", "password": "xxx"}`) val := gjson.Get(r.Body.String(), "error") - assert.Equal(t, "Invalid user name or password", val.String()) + assert.Equal(t, i18n.Msg(i18n.ErrInvalidCredentials), val.String()) assert.Equal(t, http.StatusBadRequest, r.Code) }) } diff --git a/internal/entity/person.go b/internal/entity/person.go index ce32db349..1e418ac9e 100644 --- a/internal/entity/person.go +++ b/internal/entity/person.go @@ -269,13 +269,27 @@ func (m *Person) InvalidPassword(password string) bool { return true } + time.Sleep(time.Second * 5 * time.Duration(m.LoginAttempts)) + pw := FindPassword(m.PersonUID) if pw == nil { return true } - return pw.InvalidPassword(password) + if pw.InvalidPassword(password) { + if err := Db().Model(m).UpdateColumn("login_attempts", gorm.Expr("login_attempts + ?", 1)).Error; err != nil { + log.Errorf("person: %s (update login attempts)", err) + } + + return true + } + + if err := Db().Model(m).Updates(map[string]interface{}{"login_attempts": 0, "login_at": Timestamp()}).Error; err != nil { + log.Errorf("person: %s (update last login)", err) + } + + return false } // Role returns the user role for ACL permission checks. diff --git a/internal/i18n/messages.go b/internal/i18n/messages.go index 6b8ba409c..bfbc20231 100644 --- a/internal/i18n/messages.go +++ b/internal/i18n/messages.go @@ -28,6 +28,8 @@ const ( ErrNoAlbumsSelected ErrNoFilesForDownload ErrZipFailed + ErrInvalidCredentials + ErrInvalidLink MsgChangesSaved MsgAlbumCreated @@ -93,6 +95,8 @@ var Messages = MessageMap{ ErrNoAlbumsSelected: gettext("No albums selected"), ErrNoFilesForDownload: gettext("No files available for download"), ErrZipFailed: gettext("Failed to create zip file"), + ErrInvalidCredentials: gettext("Invalid credentials"), + ErrInvalidLink: gettext("Invalid link"), // Info and confirmation messages: MsgChangesSaved: gettext("Changes successfully saved"),