chore: remove username field from user table

This commit is contained in:
Steven 2023-06-23 01:01:22 +08:00
parent 8dfae8a6aa
commit 2aae515544
11 changed files with 52 additions and 78 deletions

View File

@ -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{})

View File

@ -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),
}
}

View File

@ -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")
}

View File

@ -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'
);

View File

@ -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'
);

View File

@ -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 {

View File

@ -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 {

View File

@ -4,16 +4,16 @@ export function getSystemStatus() {
return axios.get<SystemStatus>("/api/v1/status");
}
export function signin(username: string, password: string) {
export function signin(email: string, password: string) {
return axios.post<User>("/api/v1/auth/signin", {
username,
email,
password,
});
}
export function signup(username: string, password: string) {
export function signup(email: string, password: string) {
return axios.post<User>("/api/v1/auth/signup", {
username,
email,
password,
});
}

View File

@ -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<HTMLInputElement>) => {
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setUsername(text);
setEmail(text);
};
const handlePasswordInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -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 = () => {
</div>
<div className={`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading ? "opacity-80" : ""}`}>
<div className="w-full flex flex-col mb-2">
<span className="leading-8 mb-1 text-gray-600">Username</span>
<Input className="w-full py-3" type="username" value={username} onChange={handleUsernameInputChanged} />
<span className="leading-8 mb-1 text-gray-600">Email</span>
<Input className="w-full py-3" type="email" value={email} onChange={handleEmailInputChanged} />
</div>
<div className="w-full flex flex-col mb-2">
<span className="leading-8 text-gray-600">Password</span>

View File

@ -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 () => {

View File

@ -9,9 +9,8 @@ interface User {
updatedTs: TimeStamp;
rowStatus: RowStatus;
username: string;
nickname: string;
email: string;
nickname: string;
role: Role;
}