diff --git a/api/user.go b/api/user.go index 68e6d41..bd82426 100644 --- a/api/user.go +++ b/api/user.go @@ -18,6 +18,7 @@ type User struct { Email string `json:"email"` Name string `json:"name"` PasswordHash string `json:"-"` + OpenID string `json:"openId"` UserSettingList []*UserSetting `json:"userSettingList"` } @@ -26,6 +27,7 @@ type UserCreate struct { Name string `json:"name"` Password string `json:"password"` PasswordHash string + OpenID string } func (create UserCreate) Validate() error { @@ -53,6 +55,8 @@ type UserPatch struct { Name *string `json:"name"` Password *string `json:"password"` PasswordHash *string + ResetOpenID *bool `json:"resetOpenId"` + OpenID *string } type UserFind struct { @@ -62,8 +66,9 @@ type UserFind struct { RowStatus *RowStatus `json:"rowStatus"` // Domain specific fields - Email *string `json:"email"` - Name *string `json:"name"` + Email *string `json:"email"` + Name *string `json:"name"` + OpenID *string `json:"openId"` } type UserDelete struct { diff --git a/server/acl.go b/server/acl.go index 7a9887a..dae7933 100644 --- a/server/acl.go +++ b/server/acl.go @@ -64,6 +64,25 @@ func aclMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc { return next(c) } + { + // If there is openId in query string and related user is found, then skip auth. + openID := c.QueryParam("openId") + if openID != "" { + userFind := &api.UserFind{ + OpenID: &openID, + } + user, err := s.Store.FindUser(ctx, userFind) + if err != nil && common.ErrorCode(err) != common.NotFound { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by open_id").SetInternal(err) + } + if user != nil { + // Stores userID into context. + c.Set(getUserIDContextKey(), user.ID) + return next(c) + } + } + } + { sess, _ := session.Get("session", c) userIDValue := sess.Values[userIDContextKey] diff --git a/server/auth.go b/server/auth.go index d5c5523..18b72c2 100644 --- a/server/auth.go +++ b/server/auth.go @@ -61,6 +61,7 @@ func (s *Server) registerAuthRoutes(g *echo.Group) { Email: signup.Email, Name: signup.Name, Password: signup.Password, + OpenID: common.GenUUID(), } if err := userCreate.Validate(); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format.").SetInternal(err) diff --git a/server/user.go b/server/user.go index e510e7e..e8b1aa3 100644 --- a/server/user.go +++ b/server/user.go @@ -59,34 +59,6 @@ func (s *Server) registerUserRoutes(g *echo.Group) { return nil }) - g.POST("/user/setting", func(c echo.Context) error { - ctx := c.Request().Context() - userID, ok := c.Get(getUserIDContextKey()).(int) - if !ok { - return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session") - } - - userSettingUpsert := &api.UserSettingUpsert{} - if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err) - } - if err := userSettingUpsert.Validate(); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err) - } - - userSettingUpsert.UserID = userID - userSetting, err := s.Store.UpsertUserSetting(ctx, userSettingUpsert) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err) - } - - c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) - if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(userSetting)); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user setting response").SetInternal(err) - } - return nil - }) - g.GET("/user/:id", func(c echo.Context) error { ctx := c.Request().Context() id, err := strconv.Atoi(c.Param("id")) @@ -151,6 +123,11 @@ func (s *Server) registerUserRoutes(g *echo.Group) { userPatch.PasswordHash = &passwordHashStr } + if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID { + uuid := common.GenUUID() + userPatch.OpenID = &uuid + } + user, err := s.Store.PatchUser(ctx, userPatch) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err) @@ -163,6 +140,34 @@ func (s *Server) registerUserRoutes(g *echo.Group) { return nil }) + g.POST("/user/setting", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session") + } + + userSettingUpsert := &api.UserSettingUpsert{} + if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err) + } + if err := userSettingUpsert.Validate(); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err) + } + + userSettingUpsert.UserID = userID + userSetting, err := s.Store.UpsertUserSetting(ctx, userSettingUpsert) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err) + } + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(userSetting)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user setting response").SetInternal(err) + } + return nil + }) + g.DELETE("/user/:id", func(c echo.Context) error { ctx := c.Request().Context() currentUserID, ok := c.Get(getUserIDContextKey()).(int) diff --git a/store/db/migration/dev/LATEST__SCHEMA.sql b/store/db/migration/dev/LATEST__SCHEMA.sql index 6915dc9..0bc0d87 100644 --- a/store/db/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/migration/dev/LATEST__SCHEMA.sql @@ -55,7 +55,8 @@ CREATE TABLE user ( row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL', email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, - password_hash TEXT NOT NULL + password_hash TEXT NOT NULL, + open_id TEXT NOT NULL UNIQUE ); INSERT INTO @@ -75,6 +76,12 @@ WHERE rowid = old.rowid; END; +CREATE INDEX user_id_index ON user(id); + +CREATE UNIQUE INDEX user_email_index ON user(email); + +CREATE UNIQUE INDEX user_open_id_index ON user(open_id); + -- user_setting CREATE TABLE user_setting ( user_id INTEGER NOT NULL, diff --git a/store/db/seed/10001__user.sql b/store/db/seed/10001__user.sql index 297559b..ed4074d 100644 --- a/store/db/seed/10001__user.sql +++ b/store/db/seed/10001__user.sql @@ -3,7 +3,8 @@ INSERT INTO `id`, `email`, `name`, - `password_hash` + `password_hash`, + `open_id` ) VALUES ( @@ -11,39 +12,6 @@ VALUES 'demo@iamcorgi.com', 'Demo Host', -- raw password: secret - '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' + '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK', + 'demo_open_id' ); - -INSERT INTO - user ( - `id`, - `email`, - `name`, - `password_hash` - ) -VALUES - ( - 102, - 'jack@iamcorgi.com', - 'Jack', - -- raw password: secret - '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' - ); - -INSERT INTO - user ( - `id`, - `row_status`, - `email`, - `name`, - `password_hash` - ) -VALUES - ( - 103, - 'ARCHIVED', - 'bob@iamcorgi.com', - 'Bob', - -- raw password: secret - '$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK' - ); \ No newline at end of file diff --git a/store/user.go b/store/user.go index 56b3b49..4b70e66 100644 --- a/store/user.go +++ b/store/user.go @@ -24,6 +24,7 @@ type userRaw struct { Email string Name string PasswordHash string + OpenID string } func (raw *userRaw) toUser() *api.User { @@ -37,6 +38,7 @@ func (raw *userRaw) toUser() *api.User { Email: raw.Email, Name: raw.Name, PasswordHash: raw.PasswordHash, + OpenID: raw.OpenID, } } @@ -174,24 +176,27 @@ func createUser(ctx context.Context, tx *sql.Tx, create *api.UserCreate) (*userR INSERT INTO user ( email, name, - password_hash + password_hash, + open_id ) - VALUES (?, ?, ?) - RETURNING id, email, name, password_hash, created_ts, updated_ts, row_status + VALUES (?, ?, ?, ?) + RETURNING id, created_ts, updated_ts, row_status, email, name, password_hash, open_id ` var userRaw userRaw if err := tx.QueryRowContext(ctx, query, create.Email, create.Name, create.PasswordHash, + create.OpenID, ).Scan( &userRaw.ID, - &userRaw.Email, - &userRaw.Name, - &userRaw.PasswordHash, &userRaw.CreatedTs, &userRaw.UpdatedTs, &userRaw.RowStatus, + &userRaw.Email, + &userRaw.Name, + &userRaw.PasswordHash, + &userRaw.OpenID, ); err != nil { return nil, FormatError(err) } @@ -214,6 +219,9 @@ func patchUser(ctx context.Context, tx *sql.Tx, patch *api.UserPatch) (*userRaw, if v := patch.PasswordHash; v != nil { set, args = append(set, "password_hash = ?"), append(args, *v) } + if v := patch.OpenID; v != nil { + set, args = append(set, "open_id = ?"), append(args, *v) + } args = append(args, patch.ID) @@ -221,7 +229,7 @@ func patchUser(ctx context.Context, tx *sql.Tx, patch *api.UserPatch) (*userRaw, UPDATE user SET ` + strings.Join(set, ", ") + ` WHERE id = ? - RETURNING id, created_ts, updated_ts, row_status, email, name, password_hash + RETURNING id, created_ts, updated_ts, row_status, email, name, password_hash, open_id ` row, err := tx.QueryContext(ctx, query, args...) if err != nil { @@ -239,6 +247,7 @@ func patchUser(ctx context.Context, tx *sql.Tx, patch *api.UserPatch) (*userRaw, &userRaw.Email, &userRaw.Name, &userRaw.PasswordHash, + &userRaw.OpenID, ); err != nil { return nil, FormatError(err) } @@ -265,6 +274,9 @@ func findUserList(ctx context.Context, tx *sql.Tx, find *api.UserFind) ([]*userR if v := find.Name; v != nil { where, args = append(where, "name = ?"), append(args, *v) } + if v := find.OpenID; v != nil { + where, args = append(where, "open_id = ?"), append(args, *v) + } query := ` SELECT @@ -274,7 +286,8 @@ func findUserList(ctx context.Context, tx *sql.Tx, find *api.UserFind) ([]*userR row_status, email, name, - password_hash + password_hash, + open_id FROM user WHERE ` + strings.Join(where, " AND ") + ` ORDER BY updated_ts DESC, created_ts DESC, row_status DESC @@ -296,6 +309,7 @@ func findUserList(ctx context.Context, tx *sql.Tx, find *api.UserFind) ([]*userR &userRaw.Email, &userRaw.Name, &userRaw.PasswordHash, + &userRaw.OpenID, ); err != nil { return nil, FormatError(err) }