Skip to content

Commit

Permalink
[feature] Add first iteration of a user panel at /user (#736)
Browse files Browse the repository at this point in the history
* start work on user panel

* parse source first before checking if empty form

* newline

* set avi + header nicely

* add posts settings

* render signin a bit nicer on mobile

* return OK json on successful change

* return unauthorized on bad password

* clarify message on insecure password

* make login a bit prettier

* add alt text + border round image previews

* add logout button

* add password change

* styling updates

* redirect /auth/edit to /user

* update tests

* fix validation tests

* better labels, link to more info

* make submit button generic component

* move submit button inside forms

* add autocomplete labels to password fields

* fix indentation (thx eslint)

* update eslintrc

* eslint: no-unescaped-entities

* initial deduplication between user and admin panel

* add default status/post format setting

* user panel styling for inputs

* update user panel styling, include normalize css

* add placeholder text

* input padding

Co-authored-by: f0x <f0x@cthu.lu>
  • Loading branch information
tsmethurst and f0x52 committed Aug 8, 2022
1 parent 4722970 commit 117888c
Show file tree
Hide file tree
Showing 29 changed files with 932 additions and 203 deletions.
8 changes: 8 additions & 0 deletions internal/api/client/auth/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
Expand Down Expand Up @@ -142,6 +143,12 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
return
}

instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())
if errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}

// the authorize template will display a form to the user where they can get some information
// about the app that's trying to authorize, and the scope of the request.
// They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler
Expand All @@ -151,6 +158,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
"redirect": redirect,
"scope": scope,
"user": acct.Username,
"instance": instance,
})
}

Expand Down
11 changes: 10 additions & 1 deletion internal/api/client/auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
Expand All @@ -50,8 +51,16 @@ func (m *Module) SignInGETHandler(c *gin.Context) {
}

if m.idp == nil {
instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())
if errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}

// no idp provider, use our own funky little sign in page
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{})
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{
"instance": instance,
})
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/user/passwordchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,5 @@ func (m *Module) PasswordChangePOSTHandler(c *gin.Context) {
return
}

c.Status(http.StatusOK)
c.JSON(http.StatusOK, gin.H{"status": "OK"})
}
6 changes: 3 additions & 3 deletions internal/api/client/user/passwordchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ func (suite *PasswordChangeTestSuite) TestPasswordIncorrectOldPassword() {
suite.userModule.PasswordChangePOSTHandler(ctx)

// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
suite.EqualValues(http.StatusUnauthorized, recorder.Code)

result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
suite.NoError(err)
suite.Equal(`{"error":"Bad Request: old password did not match"}`, string(b))
suite.Equal(`{"error":"Unauthorized: old password was incorrect"}`, string(b))
}

func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
Expand Down Expand Up @@ -153,7 +153,7 @@ func (suite *PasswordChangeTestSuite) TestPasswordWeakNewPassword() {
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
suite.NoError(err)
suite.Equal(`{"error":"Bad Request: password is 94% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"}`, string(b))
suite.Equal(`{"error":"Bad Request: password is only 94% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"}`, string(b))
}

func TestPasswordChangeTestSuite(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/processing/user/changepassword.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (

func (p *processor) ChangePassword(ctx context.Context, user *gtsmodel.User, oldPassword string, newPassword string) gtserror.WithCode {
if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(oldPassword)); err != nil {
return gtserror.NewErrorBadRequest(err, "old password did not match")
return gtserror.NewErrorUnauthorized(err, "old password was incorrect")
}

if err := validate.NewPassword(newPassword); err != nil {
Expand Down
26 changes: 22 additions & 4 deletions internal/processing/user/changepassword_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,35 @@ func (suite *ChangePasswordTestSuite) TestChangePasswordIncorrectOld() {

errWithCode := suite.user.ChangePassword(context.Background(), user, "ooooopsydoooopsy", "verygoodnewpassword")
suite.EqualError(errWithCode, "crypto/bcrypt: hashedPassword is not the hash of the given password")
suite.Equal(http.StatusBadRequest, errWithCode.Code())
suite.Equal("Bad Request: old password did not match", errWithCode.Safe())
suite.Equal(http.StatusUnauthorized, errWithCode.Code())
suite.Equal("Unauthorized: old password was incorrect", errWithCode.Safe())

// get user from the db again
dbUser := &gtsmodel.User{}
err := suite.db.GetByID(context.Background(), user.ID, dbUser)
suite.NoError(err)

// check the password has not changed
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("password"))
suite.NoError(err)
}

func (suite *ChangePasswordTestSuite) TestChangePasswordWeakNew() {
user := suite.testUsers["local_account_1"]

errWithCode := suite.user.ChangePassword(context.Background(), user, "password", "1234")
suite.EqualError(errWithCode, "password is 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password")
suite.EqualError(errWithCode, "password is only 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password")
suite.Equal(http.StatusBadRequest, errWithCode.Code())
suite.Equal("Bad Request: password is 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password", errWithCode.Safe())
suite.Equal("Bad Request: password is only 11% strength, try including more special characters, using lowercase letters, using uppercase letters or using a longer password", errWithCode.Safe())

// get user from the db again
dbUser := &gtsmodel.User{}
err := suite.db.GetByID(context.Background(), user.ID, dbUser)
suite.NoError(err)

// check the password has not changed
err = bcrypt.CompareHashAndPassword([]byte(dbUser.EncryptedPassword), []byte("password"))
suite.NoError(err)
}

func TestChangePasswordTestSuite(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/validate/formvalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func NewPassword(password string) error {
return errors.New(strings.ReplaceAll(
err.Error(),
"insecure password",
fmt.Sprintf("password is %d%% strength", percent)))
fmt.Sprintf("password is only %d%% strength", percent)))
}

return nil // pasword OK
Expand Down
8 changes: 4 additions & 4 deletions internal/validate/formvalidation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@ func (suite *ValidationTestSuite) TestCheckPasswordStrength() {

err = validate.NewPassword(terriblePassword)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), errors.New("password is 62% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"), err)
assert.Equal(suite.T(), errors.New("password is only 62% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"), err)
}

err = validate.NewPassword(weakPassword)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), errors.New("password is 95% strength, try including more special characters, using numbers or using a longer password"), err)
assert.Equal(suite.T(), errors.New("password is only 95% strength, try including more special characters, using numbers or using a longer password"), err)
}

err = validate.NewPassword(shortPassword)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), errors.New("password is 39% strength, try including more special characters or using a longer password"), err)
assert.Equal(suite.T(), errors.New("password is only 39% strength, try including more special characters or using a longer password"), err)
}

err = validate.NewPassword(specialPassword)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), errors.New("password is 53% strength, try including more special characters or using a longer password"), err)
assert.Equal(suite.T(), errors.New("password is only 53% strength, try including more special characters or using a longer password"), err)
}

err = validate.NewPassword(longPassword)
Expand Down
4 changes: 4 additions & 0 deletions internal/web/panels.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (m *Module) UserPanelHandler(c *gin.Context) {
assetsPath + "/Fork-Awesome/css/fork-awesome.min.css",
assetsPath + "/dist/_colors.css",
assetsPath + "/dist/base.css",
assetsPath + "/dist/panels-base.css",
assetsPath + "/dist/panels-user-style.css",
},
"javascript": []string{
Expand All @@ -63,6 +64,9 @@ func (m *Module) AdminPanelHandler(c *gin.Context) {
"instance": instance,
"stylesheets": []string{
assetsPath + "/Fork-Awesome/css/fork-awesome.min.css",
assetsPath + "/dist/_colors.css",
assetsPath + "/dist/base.css",
assetsPath + "/dist/panels-base.css",
assetsPath + "/dist/panels-admin-style.css",
},
"javascript": []string{
Expand Down
6 changes: 5 additions & 1 deletion internal/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,14 @@ func (m *Module) Route(s router.Router) error {
})

s.AttachHandler(http.MethodGet, userPanelpath, m.UserPanelHandler)
// redirect /settings/ to /settings
// redirect /user/ to /user
s.AttachHandler(http.MethodGet, userPanelpath+"/", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, userPanelpath)
})
// redirect /auth/edit to /user
s.AttachHandler(http.MethodGet, "/auth/edit", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, userPanelpath)
})

// serve front-page
s.AttachHandler(http.MethodGet, "/", m.baseHandler)
Expand Down
9 changes: 7 additions & 2 deletions web/source/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"use strict";

module.exports = {
"extends": ["@f0x52/eslint-config-react"]
};
"extends": ["@f0x52/eslint-config-react"],
"rules": {
"react/prop-types": "off"
}
};
5 changes: 4 additions & 1 deletion web/source/css/_colors.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ $bg_trans: color-mod($sloth_gray2 alpha(62%));

$bg_accent: $sloth_gray2_lighter3;
$fg_accent: $lightblue;
$border_accent: $sloth_orange2;

/* Color variables as used in a specific location */

Expand All @@ -70,4 +71,6 @@ $status_info_fg: #CBCBD7;
$boxshadow: 0 0.4rem 1rem -0.1rem rgba(0,0,0,0.15);
$boxshadow_border: 0.08rem solid $sloth_gray2_darker5;

$profile_avatar_border: 0.2rem solid $sloth_orange2;
$profile_avatar_border: 0.2rem solid $border_accent;

$input_bg: $sloth_gray2_darker3;
49 changes: 41 additions & 8 deletions web/source/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

@import "modern-normalize/modern-normalize.css";

@font-face {
font-family: "Noto Sans";
font-weight: 400;
Expand Down Expand Up @@ -48,6 +50,10 @@ body {
position: relative;
}

.hidden {
display: none;
}

.page {
position: absolute;
display: grid;
Expand Down Expand Up @@ -215,13 +221,26 @@ section.apps {

section.login {
form {
display: inline-grid;
grid-template-columns: auto 100%;
grid-gap: 0.7rem;
display: flex;
flex-direction: column;
gap: 1rem;


padding-bottom: 1rem;
padding-top: 1rem;

label, input {
padding-left: 0.2rem;
}

button {
place-self: center;
grid-column: 2;
.labelinput {
display: flex;
flex-direction: column;
gap: 0.4rem;
}

.btn {
margin-top: 1rem;
}
}
}
Expand All @@ -245,11 +264,25 @@ section.error {
}

input, select, textarea {
border: 1px solid $fg;
box-sizing: border-box;
border: 0.15rem solid $border_accent;
border-radius: 0.1rem;
color: $fg;
background: $bg;
/* background: $input_bg; */
background: $bg_accent;
width: 100%;
font-family: 'Noto Sans', sans-serif;
font-size: 1rem;
padding: 0.3rem;

&:focus {
border-color: $fg_accent;
}
}

input, textarea {
padding-top: 0.1rem;
padding-bottom: 0.1rem;
}

footer {
Expand Down
30 changes: 30 additions & 0 deletions web/source/lib/submit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

const React = require("react");

module.exports = function Submit({onClick, label, errorMsg, statusMsg}) {
return (
<div className="messagebutton">
<button type="submit" onClick={onClick}>{ label }</button>
<div className="error accent">{errorMsg ? errorMsg : statusMsg}</div>
</div>
);
};
1 change: 1 addition & 0 deletions web/source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"from2-string": "^1.1.0",
"icssify": "^2.0.0",
"js-file-download": "^0.4.12",
"modern-normalize": "^1.1.0",
"photoswipe": "^5.3.0",
"photoswipe-dynamic-caption-plugin": "^1.2.4",
"postcss-color-mod-function": "^3.0.3",
Expand Down

0 comments on commit 117888c

Please sign in to comment.