diff --git a/cmd/users.go b/cmd/users.go index 54ddb3c8d..fbcf470df 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -2,12 +2,19 @@ package main import ( "net/http" + "regexp" "strconv" "strings" "time" + "github.com/knadh/listmonk/internal/utils" "github.com/knadh/listmonk/models" "github.com/labstack/echo/v4" + "gopkg.in/volatiletech/null.v6" +) + +var ( + reUsername = regexp.MustCompile("^[a-zA-Z0-9_\\-\\.]+$") ) // handleGetUsers retrieves users. @@ -53,22 +60,33 @@ func handleCreateUser(c echo.Context) error { u.Username = strings.TrimSpace(u.Username) u.Name = strings.TrimSpace(u.Name) - u.Email = strings.TrimSpace(u.Email) - - if u.Name == "" { - u.Name = u.Username - } + email := strings.TrimSpace(u.Email.String) + // Validate fields. if !strHasLen(u.Username, 1, stdInputMaxLen) { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "username")) } - - if u.PasswordLogin { - if !strHasLen(u.Password.String, 8, stdInputMaxLen) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) + if !reUsername.MatchString(u.Username) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "username")) + } + if u.Type != models.UserTypeAPI { + if !utils.ValidateEmail(email) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "email")) + } + if u.PasswordLogin { + if !strHasLen(u.Password.String, 8, stdInputMaxLen) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) + } } + + u.Email = null.String{String: email, Valid: true} + } + + if u.Name == "" { + u.Name = u.Username } + // Create the user in the database. out, err := app.core.CreateUser(u) if err != nil { return err @@ -97,34 +115,49 @@ func handleUpdateUser(c echo.Context) error { // Validate. u.Username = strings.TrimSpace(u.Username) u.Name = strings.TrimSpace(u.Name) - u.Email = strings.TrimSpace(u.Email) - - if u.Name == "" { - u.Name = u.Username - } + email := strings.TrimSpace(u.Email.String) + // Validate fields. if !strHasLen(u.Username, 1, stdInputMaxLen) { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "username")) } + if !reUsername.MatchString(u.Username) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "username")) + } - if u.PasswordLogin { - if u.Password.String != "" { + if u.Type != models.UserTypeAPI { + if !utils.ValidateEmail(email) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "email")) + } + if u.PasswordLogin && u.Password.String != "" { if !strHasLen(u.Password.String, 8, stdInputMaxLen) { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) } - } else { - // Get the existing user for password validation. - user, err := app.core.GetUser(id) - if err != nil { - return err - } - // If password login is enabled, but there's no password in the DB and there's no incoming - // password, throw an error. - if !user.HasPassword { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) + if u.Password.String != "" { + if !strHasLen(u.Password.String, 8, stdInputMaxLen) { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) + } + } else { + // Get the existing user for password validation. + user, err := app.core.GetUser(id) + if err != nil { + return err + } + + // If password login is enabled, but there's no password in the DB and there's no incoming + // password, throw an error. + if !user.HasPassword { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidFields", "name", "password")) + } } } + + u.Email = null.String{String: email, Valid: true} + } + + if u.Name == "" { + u.Name = u.Username } out, err := app.core.UpdateUser(id, u) diff --git a/frontend/fontello/config.json b/frontend/fontello/config.json index 90bcca42a..9cb2d79c2 100755 --- a/frontend/fontello/config.json +++ b/frontend/fontello/config.json @@ -600,6 +600,20 @@ "code": 59431, "src": "typicons" }, + { + "uid": "77025195d19e048302e8943e2da4cc75", + "css": "account-outline", + "code": 983059, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M500 166Q568.4 166 617.2 214.8T666 333 617.2 451.2 500 500 382.8 451.2 334 333 382.8 214.8 500 166ZM500 250Q464.8 250 440.4 274.4T416 333 440.4 391.6 500 416 559.6 391.6 584 333 559.6 274.4 500 250ZM500 541Q562.5 541 636.7 560.5 720.7 582 771.5 615.2 834 656.3 834 709V834H166V709Q166 656.3 228.5 615.2 279.3 582 363.3 560.5 437.5 541 500 541ZM500 621.1Q441.4 621.1 378.9 636.7 324.2 652.3 285.2 673.8T246.1 709V753.9H753.9V709Q753.9 695.3 714.8 673.8T621.1 636.7Q558.6 621.1 500 621.1Z", + "width": 1000 + }, + "search": [ + "account-outline" + ] + }, { "uid": "f4ad3f6d071a0bfb3a8452b514ed0892", "css": "vector-square", @@ -838,20 +852,6 @@ "account-off" ] }, - { - "uid": "77025195d19e048302e8943e2da4cc75", - "css": "account-outline", - "code": 983059, - "src": "custom_icons", - "selected": false, - "svg": { - "path": "M500 166Q568.4 166 617.2 214.8T666 333 617.2 451.2 500 500 382.8 451.2 334 333 382.8 214.8 500 166ZM500 250Q464.8 250 440.4 274.4T416 333 440.4 391.6 500 416 559.6 391.6 584 333 559.6 274.4 500 250ZM500 541Q562.5 541 636.7 560.5 720.7 582 771.5 615.2 834 656.3 834 709V834H166V709Q166 656.3 228.5 615.2 279.3 582 363.3 560.5 437.5 541 500 541ZM500 621.1Q441.4 621.1 378.9 636.7 324.2 652.3 285.2 673.8T246.1 709V753.9H753.9V709Q753.9 695.3 714.8 673.8T621.1 636.7Q558.6 621.1 500 621.1Z", - "width": 1000 - }, - "search": [ - "account-outline" - ] - }, { "uid": "571120b7ff63feb71df85710d019302c", "css": "account-plus", diff --git a/frontend/src/assets/icons/fontello.css b/frontend/src/assets/icons/fontello.css index 41a0f96e3..ee4874e2e 100644 --- a/frontend/src/assets/icons/fontello.css +++ b/frontend/src/assets/icons/fontello.css @@ -75,6 +75,7 @@ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } + .mdi-view-dashboard-variant-outline:before { content: '\e800'; } /* '' */ .mdi-format-list-bulleted-square:before { content: '\e801'; } /* '' */ .mdi-newspaper-variant-outline:before { content: '\e802'; } /* '' */ @@ -115,6 +116,7 @@ .mdi-email-bounce:before { content: '\e825'; } /* '' */ .mdi-speedometer:before { content: '\e826'; } /* '' */ .mdi-warning-empty:before { content: '\e827'; } /* '' */ +.mdi-account-outline:before { content: ''; } /* '\f0013' */ .mdi-code:before { content: ''; } /* '\f0169' */ .mdi-logout-variant:before { content: ''; } /* '\f05fd' */ .mdi-wrench-outline:before { content: ''; } /* '\f0be0' */ diff --git a/frontend/src/assets/icons/fontello.woff2 b/frontend/src/assets/icons/fontello.woff2 index caa51743a..5f729eea5 100755 Binary files a/frontend/src/assets/icons/fontello.woff2 and b/frontend/src/assets/icons/fontello.woff2 differ diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss index 3fd67bb74..73fb48609 100644 --- a/frontend/src/assets/style.scss +++ b/frontend/src/assets/style.scss @@ -563,21 +563,21 @@ body.is-noscroll { color: $grey; } - &.private, &.scheduled, &.paused, &.tx { + &.private, &.scheduled, &.paused, &.tx, &.api { $color: #ed7b00; color: $color; background: #fff7e6; border: 1px solid lighten($color, 37%); box-shadow: 1px 1px 0 lighten($color, 37%); } - &.public, &.running, &.list, &.campaign, &.super { + &.public, &.running, &.list, &.campaign, &.user { $color: $primary; color: lighten($color, 20%);; background: #e6f7ff; border: 1px solid lighten($color, 42%); box-shadow: 1px 1px 0 lighten($color, 42%); } - &.finished, &.enabled, &.status-confirmed { + &.finished, &.enabled, &.status-confirmed, &.super { $color: $green; color: $color; background: #f6ffed; @@ -897,6 +897,15 @@ section.users { min-width: 100px !important; } } +.user-api-token .copy-text { + background: rgba($green, .1); + display: block; + width: 100%; + border-radius: 3px; + padding: 15px; + font-size: 1.2rem; + color: $green; +} /* C3 charting lib */ diff --git a/frontend/src/views/SubscriberForm.vue b/frontend/src/views/SubscriberForm.vue index bb027688b..89c28ff70 100644 --- a/frontend/src/views/SubscriberForm.vue +++ b/frontend/src/views/SubscriberForm.vue @@ -33,7 +33,8 @@
{{ $t('users.apiOneTimeToken') }}
+