From 2aae51554485fe903c215f5df65b8b3b3d521ab9 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 23 Jun 2023 01:01:22 +0800 Subject: [PATCH] chore: remove username field from user table --- api/v1/auth.go | 18 +++++----- api/v1/user.go | 18 ++++------ server/auth/auth.go | 16 ++++----- store/db/migration/dev/LATEST__SCHEMA.sql | 3 +- store/db/migration/prod/LATEST__SCHEMA.sql | 3 +- store/user.go | 42 ++++++++-------------- test/store/user_test.go | 3 +- web/src/helpers/api.ts | 8 ++--- web/src/pages/Auth.tsx | 14 ++++---- web/src/pages/UserDetail.tsx | 2 -- web/src/types/modules/user.d.ts | 3 +- 11 files changed, 52 insertions(+), 78 deletions(-) diff --git a/api/v1/auth.go b/api/v1/auth.go index c744fd3..56f1e4b 100644 --- a/api/v1/auth.go +++ b/api/v1/auth.go @@ -21,12 +21,12 @@ func getUserIDContextKey() string { } type SignInRequest struct { - Username string `json:"username"` + Email string `json:"email"` Password string `json:"password"` } type SignUpRequest struct { - Username string `json:"username"` + Email string `json:"email"` Password string `json:"password"` } @@ -39,20 +39,20 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) { } user, err := s.Store.GetUser(ctx, &store.FindUser{ - Username: &signin.Username, + Email: &signin.Email, }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by username %s", signin.Username)).SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", signin.Email)).SetInternal(err) } if user == nil { - return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with username %s", signin.Username)) + return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", signin.Email)) } else if user.RowStatus == store.Archived { - return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", signin.Username)) + return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", signin.Email)) } // Compare the stored hashed password, with the hashed version of the password that was received. if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil { - return echo.NewHTTPError(http.StatusUnauthorized, "Unmatched username and password").SetInternal(err) + return echo.NewHTTPError(http.StatusUnauthorized, "Unmatched email and password").SetInternal(err) } if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil { @@ -74,8 +74,8 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) { } create := &store.User{ - Username: signup.Username, - Nickname: signup.Username, + Email: signup.Email, + Nickname: signup.Email, PasswordHash: string(passwordHash), } existingUsers, err := s.Store.ListUsers(ctx, &store.FindUser{}) diff --git a/api/v1/user.go b/api/v1/user.go index bcba726..eda6fe8 100644 --- a/api/v1/user.go +++ b/api/v1/user.go @@ -42,30 +42,25 @@ type User struct { RowStatus RowStatus `json:"rowStatus"` // Domain specific fields - Username string `json:"username"` - Nickname string `json:"nickname"` Email string `json:"email"` + Nickname string `json:"nickname"` Role Role `json:"role"` } type CreateUserRequest struct { - Username string `json:"username"` - Nickname string `json:"nickname"` Email string `json:"email"` + Nickname string `json:"nickname"` Password string `json:"password"` Role Role `json:"-"` } func (create CreateUserRequest) Validate() error { - if len(create.Username) < 3 { - return fmt.Errorf("username is too short, minimum length is 3") - } - if create.Nickname != "" && len(create.Nickname) < 3 { - return fmt.Errorf("username is too short, minimum length is 3") - } if create.Email != "" && !validateEmail(create.Email) { return fmt.Errorf("invalid email format") } + if create.Nickname != "" && len(create.Nickname) < 3 { + return fmt.Errorf("nickname is too short, minimum length is 3") + } if len(create.Password) < 3 { return fmt.Errorf("password is too short, minimum length is 3") } @@ -228,9 +223,8 @@ func convertUserFromStore(user *store.User) *User { CreatedTs: user.CreatedTs, UpdatedTs: user.UpdatedTs, RowStatus: RowStatus(user.RowStatus), - Username: user.Username, - Nickname: user.Nickname, Email: user.Email, + Nickname: user.Nickname, Role: Role(user.Role), } } diff --git a/server/auth/auth.go b/server/auth/auth.go index ed786cf..690c29d 100644 --- a/server/auth/auth.go +++ b/server/auth/auth.go @@ -46,26 +46,26 @@ type claimsMessage struct { } // GenerateAPIToken generates an API token. -func GenerateAPIToken(userName string, userID int, secret string) (string, error) { +func GenerateAPIToken(username string, userID int, secret string) (string, error) { expirationTime := time.Now().Add(apiTokenDuration) - return generateToken(userName, userID, AccessTokenAudienceName, expirationTime, []byte(secret)) + return generateToken(username, userID, AccessTokenAudienceName, expirationTime, []byte(secret)) } // GenerateAccessToken generates an access token for web. -func GenerateAccessToken(userName string, userID int, secret string) (string, error) { +func GenerateAccessToken(username string, userID int, secret string) (string, error) { expirationTime := time.Now().Add(accessTokenDuration) - return generateToken(userName, userID, AccessTokenAudienceName, expirationTime, []byte(secret)) + return generateToken(username, userID, AccessTokenAudienceName, expirationTime, []byte(secret)) } // GenerateRefreshToken generates a refresh token for web. -func GenerateRefreshToken(userName string, userID int, secret string) (string, error) { +func GenerateRefreshToken(username string, userID int, secret string) (string, error) { expirationTime := time.Now().Add(refreshTokenDuration) - return generateToken(userName, userID, RefreshTokenAudienceName, expirationTime, []byte(secret)) + return generateToken(username, userID, RefreshTokenAudienceName, expirationTime, []byte(secret)) } // GenerateTokensAndSetCookies generates jwt token and saves it to the http-only cookie. func GenerateTokensAndSetCookies(c echo.Context, user *store.User, secret string) error { - accessToken, err := GenerateAccessToken(user.Username, user.ID, secret) + accessToken, err := GenerateAccessToken(user.Email, user.ID, secret) if err != nil { return errors.Wrap(err, "failed to generate access token") } @@ -74,7 +74,7 @@ func GenerateTokensAndSetCookies(c echo.Context, user *store.User, secret string setTokenCookie(c, AccessTokenCookieName, accessToken, cookieExp) // We generate here a new refresh token and saving it to the cookie. - refreshToken, err := GenerateRefreshToken(user.Username, user.ID, secret) + refreshToken, err := GenerateRefreshToken(user.Email, user.ID, secret) if err != nil { return errors.Wrap(err, "failed to generate refresh token") } diff --git a/store/db/migration/dev/LATEST__SCHEMA.sql b/store/db/migration/dev/LATEST__SCHEMA.sql index 94f21e5..fad5573 100644 --- a/store/db/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/migration/dev/LATEST__SCHEMA.sql @@ -16,9 +16,8 @@ CREATE TABLE user ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, nickname TEXT NOT NULL, - email TEXT NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' ); diff --git a/store/db/migration/prod/LATEST__SCHEMA.sql b/store/db/migration/prod/LATEST__SCHEMA.sql index 94f21e5..fad5573 100644 --- a/store/db/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/migration/prod/LATEST__SCHEMA.sql @@ -16,9 +16,8 @@ CREATE TABLE user ( created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', - username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, nickname TEXT NOT NULL, - email TEXT NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER' ); diff --git a/store/user.go b/store/user.go index a2b7120..2a98e72 100644 --- a/store/user.go +++ b/store/user.go @@ -26,9 +26,8 @@ type User struct { RowStatus RowStatus // Domain specific fields - Username string - Nickname string Email string + Nickname string PasswordHash string Role Role } @@ -37,9 +36,8 @@ type UpdateUser struct { ID int RowStatus *RowStatus - Username *string - Nickname *string Email *string + Nickname *string PasswordHash *string Role *Role } @@ -47,9 +45,8 @@ type UpdateUser struct { type FindUser struct { ID *int RowStatus *RowStatus - Username *string - Nickname *string Email *string + Nickname *string Role *Role } @@ -66,19 +63,17 @@ func (s *Store) CreateUser(ctx context.Context, create *User) (*User, error) { query := ` INSERT INTO user ( - username, - nickname, email, + nickname, password_hash, role ) - VALUES (?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?) RETURNING id, created_ts, updated_ts, row_status ` if err := tx.QueryRowContext(ctx, query, - create.Username, - create.Nickname, create.Email, + create.Nickname, create.PasswordHash, create.Role, ).Scan( @@ -110,15 +105,12 @@ func (s *Store) UpdateUser(ctx context.Context, update *UpdateUser) (*User, erro if v := update.RowStatus; v != nil { set, args = append(set, "row_status = ?"), append(args, *v) } - if v := update.Username; v != nil { - set, args = append(set, "username = ?"), append(args, *v) + if v := update.Email; v != nil { + set, args = append(set, "email = ?"), append(args, *v) } if v := update.Nickname; v != nil { set, args = append(set, "nickname = ?"), append(args, *v) } - if v := update.Email; v != nil { - set, args = append(set, "email = ?"), append(args, *v) - } if v := update.PasswordHash; v != nil { set, args = append(set, "password_hash = ?"), append(args, *v) } @@ -134,7 +126,7 @@ func (s *Store) UpdateUser(ctx context.Context, update *UpdateUser) (*User, erro UPDATE user SET ` + strings.Join(set, ", ") + ` WHERE id = ? - RETURNING id, created_ts, updated_ts, row_status, username, nickname, email, password_hash, role + RETURNING id, created_ts, updated_ts, row_status, email, nickname, password_hash, role ` args = append(args, update.ID) user := &User{} @@ -143,9 +135,8 @@ func (s *Store) UpdateUser(ctx context.Context, update *UpdateUser) (*User, erro &user.CreatedTs, &user.UpdatedTs, &user.RowStatus, - &user.Username, - &user.Nickname, &user.Email, + &user.Nickname, &user.PasswordHash, &user.Role, ); err != nil { @@ -236,15 +227,12 @@ func listUsers(ctx context.Context, tx *sql.Tx, find *FindUser) ([]*User, error) if v := find.RowStatus; v != nil { where, args = append(where, "row_status = ?"), append(args, v.String()) } - if v := find.Username; v != nil { - where, args = append(where, "username = ?"), append(args, *v) + if v := find.Email; v != nil { + where, args = append(where, "email = ?"), append(args, *v) } if v := find.Nickname; v != nil { where, args = append(where, "nickname = ?"), append(args, *v) } - if v := find.Email; v != nil { - where, args = append(where, "email = ?"), append(args, *v) - } if v := find.Role; v != nil { where, args = append(where, "role = ?"), append(args, *v) } @@ -255,9 +243,8 @@ func listUsers(ctx context.Context, tx *sql.Tx, find *FindUser) ([]*User, error) created_ts, updated_ts, row_status, - username, - nickname, email, + nickname, password_hash, role FROM user @@ -278,9 +265,8 @@ func listUsers(ctx context.Context, tx *sql.Tx, find *FindUser) ([]*User, error) &user.CreatedTs, &user.UpdatedTs, &user.RowStatus, - &user.Username, - &user.Nickname, &user.Email, + &user.Nickname, &user.PasswordHash, &user.Role, ); err != nil { diff --git a/test/store/user_test.go b/test/store/user_test.go index 4c56893..52de45f 100644 --- a/test/store/user_test.go +++ b/test/store/user_test.go @@ -40,9 +40,8 @@ func TestUserStore(t *testing.T) { func createTestingAdminUser(ctx context.Context, ts *store.Store) (*store.User, error) { userCreate := &store.User{ Role: store.RoleAdmin, - Username: "test", - Nickname: "test_nickname", Email: "test@test.com", + Nickname: "test_nickname", } passwordHash, err := bcrypt.GenerateFromPassword([]byte("test-password"), bcrypt.DefaultCost) if err != nil { diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index 5aaaa8c..0d720c8 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -4,16 +4,16 @@ export function getSystemStatus() { return axios.get("/api/v1/status"); } -export function signin(username: string, password: string) { +export function signin(email: string, password: string) { return axios.post("/api/v1/auth/signin", { - username, + email, password, }); } -export function signup(username: string, password: string) { +export function signup(email: string, password: string) { return axios.post("/api/v1/auth/signup", { - username, + email, password, }); } diff --git a/web/src/pages/Auth.tsx b/web/src/pages/Auth.tsx index 1e99bf5..04a5fd5 100644 --- a/web/src/pages/Auth.tsx +++ b/web/src/pages/Auth.tsx @@ -9,7 +9,7 @@ import Icon from "../components/Icon"; const Auth: React.FC = () => { const navigate = useNavigate(); - const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const actionBtnLoadingState = useLoading(false); @@ -20,9 +20,9 @@ const Auth: React.FC = () => { } }, []); - const handleUsernameInputChanged = (e: React.ChangeEvent) => { + const handleEmailInputChanged = (e: React.ChangeEvent) => { const text = e.target.value as string; - setUsername(text); + setEmail(text); }; const handlePasswordInputChanged = (e: React.ChangeEvent) => { @@ -37,7 +37,7 @@ const Auth: React.FC = () => { try { actionBtnLoadingState.setLoading(); - await api.signin(username, password); + await api.signin(email, password); const user = await userService.doSignIn(); if (user) { navigate("/", { @@ -60,7 +60,7 @@ const Auth: React.FC = () => { try { actionBtnLoadingState.setLoading(); - await api.signup(username, password); + await api.signup(email, password); const user = await userService.doSignIn(); if (user) { navigate("/", { @@ -89,8 +89,8 @@ const Auth: React.FC = () => {
- Username - + Email +
Password diff --git a/web/src/pages/UserDetail.tsx b/web/src/pages/UserDetail.tsx index 8fe8300..f3687bd 100644 --- a/web/src/pages/UserDetail.tsx +++ b/web/src/pages/UserDetail.tsx @@ -16,13 +16,11 @@ const UserDetail: React.FC = () => { showChangePasswordDialog: false, }); - console.log("here"); useEffect(() => { if (!userService.getState().user) { navigate("/user/auth"); return; } - console.log("here"); }, []); const handleChangePasswordBtnClick = async () => { diff --git a/web/src/types/modules/user.d.ts b/web/src/types/modules/user.d.ts index 50fc6ae..712c517 100644 --- a/web/src/types/modules/user.d.ts +++ b/web/src/types/modules/user.d.ts @@ -9,9 +9,8 @@ interface User { updatedTs: TimeStamp; rowStatus: RowStatus; - username: string; - nickname: string; email: string; + nickname: string; role: Role; }