branch bow-v2-go updated (873d8b3 -> bd0c241)
This is an automated email from the git hooks/post-receive script. New change to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git from 873d8b3 amelioration header (login + logout) ajout d'un cookie avec les infos de l'utilisateur pour le front ('bow-user') ajout d'un service $store pour l'accès au localStorage/cookie new bd0c241 refactoring du token JWT pour stocker de l'info necessaire au serveur utilisation d'un BowUser pas seulement de son ID utilisation de query partout ou c'est possible exclusion des route 'auth' du check d'authentification The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit bd0c241eb386a47121cc8070fae09f46c62f356d Author: Benjamin <poussin@codelutin.com> Date: Thu Apr 16 01:29:20 2020 +0200 refactoring du token JWT pour stocker de l'info necessaire au serveur utilisation d'un BowUser pas seulement de son ID utilisation de query partout ou c'est possible exclusion des route 'auth' du check d'authentification Summary of changes: pkg/constant/const.go | 18 +++-- pkg/http/bookmarkResource.go | 12 +-- pkg/http/router.go | 45 +++++++---- pkg/http/userResource.go | 45 ++++++----- pkg/repository/bookmarkRepository.go | 40 ++++----- pkg/repository/database.go | 39 ++++----- pkg/repository/userRepository.go | 152 ++++++++++++++++------------------- pkg/utils/error.go | 20 ++--- pkg/utils/jwt.go | 49 ++++++++--- pkg/utils/jwt_test.go | 21 +++++ 10 files changed, 250 insertions(+), 191 deletions(-) create mode 100644 pkg/utils/jwt_test.go -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch bow-v2-go in repository bow. See https://gitlab.nuiton.org/chorem/bow.git commit bd0c241eb386a47121cc8070fae09f46c62f356d Author: Benjamin <poussin@codelutin.com> Date: Thu Apr 16 01:29:20 2020 +0200 refactoring du token JWT pour stocker de l'info necessaire au serveur utilisation d'un BowUser pas seulement de son ID utilisation de query partout ou c'est possible exclusion des route 'auth' du check d'authentification --- pkg/constant/const.go | 18 +++-- pkg/http/bookmarkResource.go | 12 +-- pkg/http/router.go | 45 +++++++---- pkg/http/userResource.go | 45 ++++++----- pkg/repository/bookmarkRepository.go | 40 ++++----- pkg/repository/database.go | 39 ++++----- pkg/repository/userRepository.go | 152 ++++++++++++++++------------------- pkg/utils/error.go | 20 ++--- pkg/utils/jwt.go | 49 ++++++++--- pkg/utils/jwt_test.go | 21 +++++ 10 files changed, 250 insertions(+), 191 deletions(-) diff --git a/pkg/constant/const.go b/pkg/constant/const.go index cc381f6..85b46a9 100644 --- a/pkg/constant/const.go +++ b/pkg/constant/const.go @@ -1,24 +1,28 @@ package constant -type httpRequestConxtKey string +import ( + "gitlab.chorem.org/chorem/bow/pkg/model" +) + +type httpRequestContexttKey string /* -UserID constant pour stocker le userID dans le context de la requete http +Nobody l'utilisateur à utiilser s'il n'y a personne d'authentifie */ -const UserID = httpRequestConxtKey("userID") +var Nobody = model.BowUser{ID: "nobody"} /* -USer le nom utiliser pour les infos user dans les cookies +User le nom utiliser pour les infos user dans les cookies */ const User = "bow-user" /* -TokenName le nom utiliser pour le token dans les cookies et les parameters de query +Token le nom utiliser pour le token dans les cookies et les parameters de query */ const Token = "bow-token" /* -TokenHeaderName le nom utiliser pour mettre dans le header de requete http (fallback Authorization) +TokenHeader le nom utiliser pour mettre dans le header de requete http (fallback Authorization) */ const TokenHeader = "x-" + Token @@ -45,4 +49,4 @@ const Fulltext = "f" /* Fulltext query parameter name for id search */ -const ID = "id" \ No newline at end of file +const ID = "id" diff --git a/pkg/http/bookmarkResource.go b/pkg/http/bookmarkResource.go index 34d66a5..56ed3dd 100644 --- a/pkg/http/bookmarkResource.go +++ b/pkg/http/bookmarkResource.go @@ -16,7 +16,7 @@ import ( ) func getBookmarks(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) query := r.URL.Query() id := query.Get(constant.ID) tags := query.Get(constant.Tags) @@ -38,7 +38,7 @@ func getBookmarks(w http.ResponseWriter, r *http.Request) { } func getBookmark(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] json, err := repository.BookmarkJSON(currentUserID, id, "", "") @@ -55,7 +55,7 @@ func getBookmark(w http.ResponseWriter, r *http.Request) { } func getTags(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) query := r.URL.Query() filter := query.Get(constant.Filter) @@ -75,7 +75,7 @@ func getTags(w http.ResponseWriter, r *http.Request) { } func addOneVisit(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] uri, err := repository.AddOneVisit(currentUserID, id) @@ -88,7 +88,7 @@ func addOneVisit(w http.ResponseWriter, r *http.Request) { } func addBookmark(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) var bookmark model.Bookmark err := json.NewDecoder(r.Body).Decode(&bookmark) @@ -122,7 +122,7 @@ func deleteBookmark(w http.ResponseWriter, r *http.Request) { updateBookmark save bookmark withour AuthenticationInfo */ func updateBookmark(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] var bookmark model.Bookmark diff --git a/pkg/http/router.go b/pkg/http/router.go index 02bc970..ab3f655 100644 --- a/pkg/http/router.go +++ b/pkg/http/router.go @@ -2,16 +2,16 @@ package http import ( "context" - "fmt" "io" "log" "net/http" "strings" "time" + "gitlab.chorem.org/chorem/bow/pkg/constant" + "gitlab.chorem.org/chorem/bow/pkg/model" "gitlab.chorem.org/chorem/bow/pkg/repository" "gitlab.chorem.org/chorem/bow/pkg/utils" - "gitlab.chorem.org/chorem/bow/pkg/constant" "github.com/gorilla/mux" ) @@ -98,7 +98,12 @@ func cors(next http.Handler) http.Handler { func authentication(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - isAppToken := false + if strings.HasSuffix(r.URL.Path, "/auth") { + next.ServeHTTP(w, r) + return + } + + canBeAppToken := false // 1 as query param query := r.URL.Query() @@ -108,7 +113,7 @@ func authentication(next http.Handler) http.Handler { if token == "" { token = r.Header.Get(constant.TokenHeader) } else { - isAppToken = true + canBeAppToken = true } // 3 as Authorization header @@ -134,24 +139,30 @@ func authentication(next http.Handler) http.Handler { } } - userID := utils.JwtVerify(token) - - if userID == "" && isAppToken { - tmp, err := repository.UserIDFromToken(token) - if err != nil { - http.Error(w, fmt.Sprintf("%s", err), 500) + var user model.BowUser + var err error + + if token != "" { + // try as jwt token first + user, err = utils.JwtVerify(token) + if err != nil && canBeAppToken { + // try as application token + user, err = repository.UserFromToken(token, "id", "login", "creationdate", "maxtagincloud", "maxresult", "actions") + if err != nil { + utils.Throw(w, err) + return + } + } else if err != nil { + utils.Throw(w, err) return } - userID = tmp - } - - if userID == "" { - userID = "nobody" + } else { + user = constant.Nobody } - log.Printf("User is '%s'\n", userID) + log.Printf("User is '%s'\n", user.ID) - ctx := context.WithValue(r.Context(), constant.UserID, userID) + ctx := context.WithValue(r.Context(), constant.User, user) r = r.WithContext(ctx) next.ServeHTTP(w, r) }) diff --git a/pkg/http/userResource.go b/pkg/http/userResource.go index 4038372..4de850a 100644 --- a/pkg/http/userResource.go +++ b/pkg/http/userResource.go @@ -10,14 +10,14 @@ import ( "time" "github.com/gorilla/mux" + "gitlab.chorem.org/chorem/bow/pkg/constant" "gitlab.chorem.org/chorem/bow/pkg/model" "gitlab.chorem.org/chorem/bow/pkg/repository" "gitlab.chorem.org/chorem/bow/pkg/utils" - "gitlab.chorem.org/chorem/bow/pkg/constant" ) /* -DeleteAuth remove cookie authentication +deleteAuth remove cookie authentication */ func deleteAuth(w http.ResponseWriter, r *http.Request) { cookie := http.Cookie{Name: constant.Token, Value: "", HttpOnly: true, MaxAge: 0} @@ -34,20 +34,25 @@ func createAuth(w http.ResponseWriter, r *http.Request) { var data map[string]string err := json.NewDecoder(r.Body).Decode(&data) if err != nil { - utils.Throw(w, utils.NewHTTPError400(err, "nobody")) + utils.Throw(w, utils.NewHTTPError400(err, constant.Nobody)) return } if id = checkLogin(id, data["email"], data["password"]); id != "" { log.Println("create token", data["email"], id) - - userJSON, err := repository.UserJSON(id, id, "id, login, creationdate, maxtagincloud, maxresult, actions") + + pseudoUser := model.BowUser{ID: id} + userJSON, err := repository.UserJSON(pseudoUser, id, "id", "login", "creationdate", "maxtagincloud", "maxresult", "actions") if err != nil { - utils.Throw(w, utils.NewHTTPError500(err, id)) + utils.Throw(w, utils.NewHTTPError500(err, pseudoUser)) return } - token := utils.JwtGenerate(id, userJSON) // si on ajoute plus d'info sensible dans le token, il faudra le chiffrer + token, err := utils.JwtGenerate(userJSON) // si on ajoute plus d'info sensible dans le token, il faudra le chiffrer + if err != nil { + utils.Throw(w, err) + return + } w.Header().Add(constant.TokenHeader, token) expiration := time.Now().AddDate(10, 0, 0) // le cookie est valide 10ans :) @@ -59,7 +64,7 @@ func createAuth(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") io.WriteString(w, userJSON) } else { - utils.Throw(w, utils.NewHTTPError("Bad id or password", "nobody", 403)) + utils.Throw(w, utils.NewHTTPError("Bad id or password", constant.Nobody, 403)) } } @@ -81,7 +86,7 @@ func checkLogin(id string, email string, password string) string { GetUser return all information on user (info, config, auth) */ func getUser(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] json, err := repository.UserJSON(currentUserID, id) @@ -119,7 +124,7 @@ func createUser(w http.ResponseWriter, r *http.Request) { } func deleteUser(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] log.Println("deleteUser", id) @@ -136,7 +141,7 @@ updateUserPassword body: {"password": "xxxx", "oldPassword": "yyyy"} */ func updateUserPassword(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] log.Println("updateUserPassword", id) @@ -161,7 +166,7 @@ body: {"name": "for application toto", "expiration": 1586081695000} return: {"token": "uuid"} */ func addUserToken(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -189,7 +194,7 @@ body: {"name": "for application toto", "expiration": 1586081695000} return: {"token": "uuid"} */ func addUserUnconfirmedEmail(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -214,7 +219,7 @@ func addUserUnconfirmedEmail(w http.ResponseWriter, r *http.Request) { } func updateUserAuthenticationInfo(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] var auth model.AuthenticationInfo @@ -234,7 +239,7 @@ func updateUserAuthenticationInfo(w http.ResponseWriter, r *http.Request) { } func updateUserActions(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] var actions []model.Action @@ -254,7 +259,7 @@ func updateUserActions(w http.ResponseWriter, r *http.Request) { } func updateUserAutoScreenshot(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -275,7 +280,7 @@ func updateUserAutoScreenshot(w http.ResponseWriter, r *http.Request) { } func updateUserAutoFavicon(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -296,7 +301,7 @@ func updateUserAutoFavicon(w http.ResponseWriter, r *http.Request) { } func updateUserMaxTagInCloud(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -317,7 +322,7 @@ func updateUserMaxTagInCloud(w http.ResponseWriter, r *http.Request) { } func updateUserMaxResult(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] @@ -338,7 +343,7 @@ func updateUserMaxResult(w http.ResponseWriter, r *http.Request) { } func confirmUserEmail(w http.ResponseWriter, r *http.Request) { - currentUserID := r.Context().Value(constant.UserID).(string) + currentUserID := r.Context().Value(constant.User).(model.BowUser) id := mux.Vars(r)["id"] token := mux.Vars(r)["token"] diff --git a/pkg/repository/bookmarkRepository.go b/pkg/repository/bookmarkRepository.go index 2bf838c..f67577e 100644 --- a/pkg/repository/bookmarkRepository.go +++ b/pkg/repository/bookmarkRepository.go @@ -17,20 +17,20 @@ import ( BookmarkJSON retourne le bookmark au format json FIXME mettre la limit a la limit de recherche souhaite par l'utilisateur */ -func BookmarkJSON(currentUserID string, id string, tags string, fulltext string) (string, error) { +func BookmarkJSON(currentUser model.BowUser, id string, tags string, fulltext string) (string, error) { var result string var err error if id != "" { q := &query{sql: `WITH __all AS (select * from bookmark where id=$1) SELECT json_agg(__all.*) as j FROM __all`} - result, err = q.QueryString(currentUserID, id) + result, err = q.QueryString(currentUser, id) } else { tagsJSON := "{" + strings.Join(strings.Fields(tags), ",") + "}" q := &query{sql: `WITH __all AS (select * from bookmark where tags @> $1::text[] LIMIT 10) SELECT json_agg(__all.*) as j FROM __all`} - result, err = q.QueryString(currentUserID, tagsJSON) + result, err = q.QueryString(currentUser, tagsJSON) } if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } return result, nil @@ -39,7 +39,7 @@ func BookmarkJSON(currentUserID string, id string, tags string, fulltext string) /* TagsJSON retourne le bookmark au format json */ -func TagsJSON(currentUserID string, filter string, withCount bool) (string, error) { +func TagsJSON(currentUser model.BowUser, filter string, withCount bool) (string, error) { var q *query if withCount { q = &query{sql: `WITH __all AS (select unnest(tags) as tag from bookmark), __some AS (select tag, count(tag) from __all where tag ilike $1 group by tag order by 2 desc) select json_agg(a) from __some a;`} @@ -47,19 +47,19 @@ func TagsJSON(currentUserID string, filter string, withCount bool) (string, erro q = &query{sql: `WITH __all AS (select distinct unnest(tags) as tag from bookmark order by 1), __some AS (select * from __all where tag ilike $1) select json_agg(tag) from __some;`} } substring := fmt.Sprintf("%%%s%%", filter) - result, err := q.QueryString(currentUserID, substring) + result, err := q.QueryString(currentUser, substring) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } return result, nil } -func AddOneVisit(currentUserID string, id string) (string, error) { +func AddOneVisit(currentUser model.BowUser, id string) (string, error) { q := &query{sql: `update bookmark SET visit = visit + 1 where id=$1 returning uri;`} - uri, err := q.QueryString(currentUserID, id) + uri, err := q.QueryString(currentUser, id) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } return uri, nil @@ -69,15 +69,15 @@ func AddOneVisit(currentUserID string, id string) (string, error) { CreateBookmark creation d'un nouveau bookmark return: id, err */ -func CreateBookmark(currentUserID string, bookmark model.Bookmark) (string, error) { +func CreateBookmark(currentUser model.BowUser, bookmark model.Bookmark) (string, error) { id, err := utils.GenUUID() if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } bookmark.ID = id - bookmark.Owner = currentUserID + bookmark.Owner = currentUser.ID bookmark.CreationDate = time.Now() bookmark.UpdateDate = bookmark.CreationDate bookmark.Visit = 0 @@ -85,14 +85,14 @@ func CreateBookmark(currentUserID string, bookmark model.Bookmark) (string, erro bookmarkAsJSON, err := json.Marshal(bookmark) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } log.Printf("Creation du bookmark %s\n", bookmarkAsJSON) q := &query{sql: `INSERT INTO bookmark AS t SELECT * FROM json_populate_record(NULL::bookmark, $1::json);`} - err = q.execOnOneRow(currentUserID, string(bookmarkAsJSON)) + err = q.execOnOneRow(currentUser, string(bookmarkAsJSON)) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } return id, err @@ -101,10 +101,10 @@ func CreateBookmark(currentUserID string, bookmark model.Bookmark) (string, erro /* UpdateBookmark creation d'un nouveau bookmark */ -func UpdateBookmark(currentUserID string, bookmark model.Bookmark) error { +func UpdateBookmark(currentUser model.BowUser, bookmark model.Bookmark) error { bookmarkAsJSON, err := json.Marshal(bookmark) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } log.Println("Update bookmark", bookmarkAsJSON) @@ -114,9 +114,9 @@ func UpdateBookmark(currentUserID string, bookmark model.Bookmark) error { (SELECT uri, description, tags, privateAlias, publicAlias, lang FROM json_populate_record(NULL::bookmark, $2::json)) WHERE id=$1`} - err = q.execOnOneRow(currentUserID, bookmark.ID, string(bookmarkAsJSON)) + err = q.execOnOneRow(currentUser, bookmark.ID, string(bookmarkAsJSON)) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } return err diff --git a/pkg/repository/database.go b/pkg/repository/database.go index f2d0725..221c313 100644 --- a/pkg/repository/database.go +++ b/pkg/repository/database.go @@ -13,6 +13,7 @@ import ( "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/tern/migrate" + "gitlab.chorem.org/chorem/bow/pkg/model" "gitlab.chorem.org/chorem/bow/pkg/utils" ) @@ -174,49 +175,49 @@ func (q *query) setPostSQL(sql string, arguments ...interface{}) { q.postsql = fmt.Sprintf(sql, arguments...) } -func (q *query) execOnOneRow(currentUserID string, arguments ...interface{}) error { +func (q *query) execOnOneRow(currentUser model.BowUser, arguments ...interface{}) error { tx, err := db.Begin(context.Background()) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } defer tx.Rollback(context.Background()) if q.presql != "" { _, err = db.Exec(context.Background(), q.presql, pgx.QuerySimpleProtocol(true)) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } } if !q.asNobody { _, err = db.Exec(context.Background(), fmt.Sprintf(` SET ROLE "%[1]s"; - `, currentUserID)) + `, currentUser.ID)) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } } modif, err := db.Exec(context.Background(), q.sql, arguments...) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } if modif.RowsAffected() != 1 { - return utils.NewHTTPError(fmt.Sprintf("User not found '%v'", arguments), currentUserID, 404) + return utils.NewHTTPError(fmt.Sprintf("User not found '%v'", arguments), currentUser, 404) } if q.postsql != "" { _, err = db.Exec(context.Background(), q.postsql, pgx.QuerySimpleProtocol(true)) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } } err = tx.Commit(context.Background()) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } return nil @@ -225,23 +226,23 @@ func (q *query) execOnOneRow(currentUserID string, arguments ...interface{}) err /* QueryString fait une requete qui ne retourne qu'une seul ligne et retourne du string (qui peut être du json) */ -func (q *query) QueryString(currentUserID string, arguments ...interface{}) (string, error) { +func (q *query) QueryString(currentUser model.BowUser, arguments ...interface{}) (string, error) { tx, err := db.Begin(context.Background()) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } defer tx.Rollback(context.Background()) if q.presql != "" { _, err = db.Exec(context.Background(), q.presql, pgx.QuerySimpleProtocol(true)) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } } - _, err = db.Exec(context.Background(), fmt.Sprintf(`SET ROLE "%[1]s";`, currentUserID)) + _, err = db.Exec(context.Background(), fmt.Sprintf(`SET ROLE "%[1]s";`, currentUser.ID)) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } row := db.QueryRow(context.Background(), q.sql, arguments...) @@ -249,29 +250,29 @@ func (q *query) QueryString(currentUserID string, arguments ...interface{}) (str var pgjson pgtype.JSON err = row.Scan(&pgjson) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } if q.postsql != "" { _, err = db.Exec(context.Background(), q.postsql, pgx.QuerySimpleProtocol(true)) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } } err = tx.Commit(context.Background()) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } var result string if pgjson.Status != pgtype.Present { - return "", utils.NewHTTPError("Not found", currentUserID, 404) + return "", utils.NewHTTPError("Not found", currentUser, 404) } err = pgjson.AssignTo(&result) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } return result, nil diff --git a/pkg/repository/userRepository.go b/pkg/repository/userRepository.go index 2a448df..6dd0b21 100644 --- a/pkg/repository/userRepository.go +++ b/pkg/repository/userRepository.go @@ -9,6 +9,7 @@ import ( "time" "github.com/jackc/pgtype" + "gitlab.chorem.org/chorem/bow/pkg/constant" "gitlab.chorem.org/chorem/bow/pkg/model" "gitlab.chorem.org/chorem/bow/pkg/utils" ) @@ -19,17 +20,7 @@ all field are send except: - password - email confirmation token */ -func UserJSON(currentUserID, id string, fields... string) (string, error) { - tx, err := db.Begin(context.Background()) - if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) - } - defer tx.Rollback(context.Background()) - _, err = db.Exec(context.Background(), fmt.Sprintf(`SET ROLE "%[1]s";`, currentUserID)) - if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) - } - +func UserJSON(currentUser model.BowUser, id string, fields ...string) (string, error) { var allFields string if len(fields) == 0 { allFields = "id, creationdate, updatedate, login, tokens, emails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" @@ -37,33 +28,17 @@ func UserJSON(currentUserID, id string, fields... string) (string, error) { allFields = strings.Join(fields, ",") } - var pgjson pgtype.JSON - row := db.QueryRow(context.Background(), fmt.Sprintf(` - WITH tmp AS (select %[1]s, (jsonb_array_elements(unconfirmedemails)::jsonb)->'email' as unconfirmedemails from bowuser where id=$1), - __all AS (select %[1]s, array_agg(unconfirmedemails) as unconfirmedemails from tmp group by %[1]s) + q := &query{sql: fmt.Sprintf(` + WITH tmp AS (select %[1]s, (jsonb_array_elements(unconfirmedemails)::jsonb)->'email' as unconfirmedemailsList from bowuser where id=$1), + __all AS (select %[1]s, array_agg(unconfirmedemailsList) as unconfirmedemailsList from tmp group by %[1]s) SELECT json_agg(__all.*) as j - FROM __all`, allFields), id) - err = row.Scan(&pgjson) - if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) - } - - err = tx.Commit(context.Background()) + FROM __all`, allFields)} + result, err := q.QueryString(currentUser, id) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } - if pgjson.Status != pgtype.Present { - return "", utils.NewHTTPError("User Not found", currentUserID, 404) - } - - var result string - err = pgjson.AssignTo(&result) - if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) - } - - result = result[1:len(result) - 1] // suppression des [] + result = result[1 : len(result)-1] // suppression des [] return result, nil } @@ -71,17 +46,35 @@ func UserJSON(currentUserID, id string, fields... string) (string, error) { /* UserIDFromToken get user id by application token */ -func UserIDFromToken(token string) (string, error) { +func UserFromToken(token string, fields ...string) (model.BowUser, error) { + currentUser := constant.Nobody + + var user model.BowUser + + var allFields string + if len(fields) == 0 { + allFields = "id, creationdate, updatedate, login, tokens, emails, authenticationinfo, autoscreenshot, autofavicon, maxtagincloud, maxresult, actions" + } else { + allFields = strings.Join(fields, ",") + } tokenJSON := fmt.Sprintf(`{"token": "%s"}`, token) - var id string - row := db.QueryRow(context.Background(), ` - select id from bowuser b + q := &query{sql: fmt.Sprintf(` + select $1 from bowuser b where exists (select * from jsonb_array_elements(tokens) as x - where x @> $1);`, tokenJSON) - err := row.Scan(&id) + where x @> $1);`, allFields)} + result, err := q.QueryString(currentUser, tokenJSON) + if err != nil { + return user, utils.NewHTTPError500(err, currentUser) + } - return id, err + result = result[1 : len(result)-1] // suppression des [] + err = json.Unmarshal([]byte(result), &user) + if err != nil { + return user, utils.NewHTTPError500(err, currentUser) + } + + return user, nil } /* @@ -111,7 +104,7 @@ func CheckUserPasswordForEmail(loginOrEmail string, password string) (string, er } if !utils.CheckPassword(password, hash) { - return "", utils.NewHTTPError(fmt.Sprintf("Password mismatch for %s", loginOrEmail), "nobody", 401) + return "", utils.NewHTTPError(fmt.Sprintf("Password mismatch for %s", loginOrEmail), constant.Nobody, 401) } var result string @@ -129,21 +122,19 @@ CreateUser retourne l'utilisateur au format json func CreateUser(login string, password string) (string, error) { hashPassword, err := utils.HashPassword(password) if err != nil { - return "",utils.NewHTTPError500(err, login) + return "", utils.NewHTTPError500(err, model.BowUser{ID: login}) } uuid, err := utils.GenUUID() if err != nil { - return "", utils.NewHTTPError500(err, login) + return "", utils.NewHTTPError500(err, model.BowUser{ID: login}) } - currentUserID := uuid - - user := model.BowUser{ + currentUser := model.BowUser{ ID: uuid, Login: login, Password: hashPassword, MaxTagInCloud: 20, MaxResult: 20, AutoFavicon: false, AutoScreenshot: false, AuthenticationInfo: model.AuthenticationInfo{DomainComponent: 2, MaxLength: 15}} - userAsJSON, err := json.Marshal(user) + userAsJSON, err := json.Marshal(currentUser) if err != nil { - return "", utils.NewHTTPError500(err, currentUserID) + return "", utils.NewHTTPError500(err, currentUser) } log.Println("create user", string(userAsJSON)) @@ -152,43 +143,43 @@ func CreateUser(login string, password string) (string, error) { q.setPostSQL(` CREATE ROLE "%[1]s"; GRANT "%[1]s" TO nobody; - `, currentUserID) + `, currentUser.ID) - err = q.execOnOneRow(currentUserID, userAsJSON) + err = q.execOnOneRow(currentUser, userAsJSON) - return user.ID, err + return currentUser.ID, err } /* DeleteUser suppression d'un nouveau bookmark */ -func DeleteUser(currentUserID string, id string) error { +func DeleteUser(currentUser model.BowUser, id string) error { q := &query{sql: `DELETE FROM bowuser WHERE id=$1;`} q.setPostSQL(` RESET ROLE; DROP ROLE IF EXISTS "%s"; - `, currentUserID) - - err := q.execOnOneRow(currentUserID, id) - + `, currentUser.ID) + + err := q.execOnOneRow(currentUser, id) + return err } /* UpdateUserPassword update user password, if old password match, or if force is true */ -func UpdateUserPassword(currentUserID string, id string, password string, oldPassword string, force bool) error { +func UpdateUserPassword(currentUser model.BowUser, id string, password string, oldPassword string, force bool) error { if !force && !CheckUserPasswordForID(id, oldPassword) { - return utils.NewHTTPError(fmt.Sprintf("Bad old password for user '%s'", id), currentUserID, 400) + return utils.NewHTTPError(fmt.Sprintf("Bad old password for user '%s'", id), currentUser, 400) } hash, err := utils.HashPassword(password) if err != nil { - return utils.NewHTTPError500(err, currentUserID) + return utils.NewHTTPError500(err, currentUser) } q := &query{sql: `update bowuser SET password=$2 where id=$1`} - err = q.execOnOneRow(currentUserID, id, hash) + err = q.execOnOneRow(currentUser, id, hash) return err } @@ -196,7 +187,7 @@ func UpdateUserPassword(currentUserID string, id string, password string, oldPas /* AddUserToken ajout un tocken d'authentification pour l'utilisateur */ -func AddUserToken(currentUserID string, id string, name string, expiration time.Time) (string, error) { +func AddUserToken(currentUser model.BowUser, id string, name string, expiration time.Time) (string, error) { token, err := utils.GenUUID() if err != nil { return "", err @@ -208,7 +199,7 @@ func AddUserToken(currentUserID string, id string, name string, expiration time. } q := &query{sql: `update bowuser SET tokens=coalesce(tokens, '[]'::jsonb) || $2::jsonb where id=$1;`} - err = q.execOnOneRow(currentUserID, id, json) + err = q.execOnOneRow(currentUser, id, json) return token, err } @@ -216,7 +207,7 @@ func AddUserToken(currentUserID string, id string, name string, expiration time. /* AddUserUnconfirmedEmail ajout d'un email non confirme, retourne le token permettant la confirmation */ -func AddUserUnconfirmedEmail(currentUserID string, id string, email string) (string, error) { +func AddUserUnconfirmedEmail(currentUser model.BowUser, id string, email string) (string, error) { token, err := utils.GenUUID() if err != nil { return "", err @@ -228,7 +219,7 @@ func AddUserUnconfirmedEmail(currentUserID string, id string, email string) (str } q := &query{sql: `update bowuser SET unconfirmedemails=coalesce(unconfirmedemails, '[]'::jsonb) || $2::jsonb where id=$1;`} - err = q.execOnOneRow(currentUserID, id, json) + err = q.execOnOneRow(currentUser, id, json) return token, nil } @@ -236,16 +227,16 @@ func AddUserUnconfirmedEmail(currentUserID string, id string, email string) (str /* UpdateUserAuthenticationInfo met a jour les infos d'authentification */ -func UpdateUserAuthenticationInfo(currentUserID string, id string, auth model.AuthenticationInfo) error { +func UpdateUserAuthenticationInfo(currentUser model.BowUser, id string, auth model.AuthenticationInfo) error { authAsJSON, err := json.Marshal(auth) if err != nil { return err - } + } q := &query{sql: `UPDATE bowuser SET (authenticationinfo) = (SELECT * FROM json_populate_record(NULL::authenticationinfo, $2::json)) WHERE id=$1`} - err = q.execOnOneRow(currentUserID, id, string(authAsJSON)) + err = q.execOnOneRow(currentUser, id, string(authAsJSON)) return err } @@ -253,14 +244,14 @@ func UpdateUserAuthenticationInfo(currentUserID string, id string, auth model.Au /* UpdateUserActions met a jour les actions utilisateur */ -func UpdateUserActions(currentUserID string, id string, actions []model.Action) error { +func UpdateUserActions(currentUser model.BowUser, id string, actions []model.Action) error { json, err := json.Marshal(actions) if err != nil { return err } q := &query{sql: `update bowuser SET actions=$2::jsonb where id=$1;`} - err = q.execOnOneRow(currentUserID, id, json) + err = q.execOnOneRow(currentUser, id, json) return err } @@ -268,9 +259,9 @@ func UpdateUserActions(currentUserID string, id string, actions []model.Action) /* UpdateUserAutoScreenshot met a jour le boolean d'auto screenshot de la page */ -func UpdateUserAutoScreenshot(currentUserID string, id string, value bool) error { +func UpdateUserAutoScreenshot(currentUser model.BowUser, id string, value bool) error { q := &query{sql: `update bowuser SET autoScreenshot=$2 where id=$1;`} - err := q.execOnOneRow(currentUserID, id, value) + err := q.execOnOneRow(currentUser, id, value) return err } @@ -278,9 +269,9 @@ func UpdateUserAutoScreenshot(currentUserID string, id string, value bool) error /* UpdateUserAutoFavicon met a jour le boolean d'auto favicon de la page */ -func UpdateUserAutoFavicon(currentUserID string, id string, value bool) error { +func UpdateUserAutoFavicon(currentUser model.BowUser, id string, value bool) error { q := &query{sql: `update bowuser SET autoFavicon=$2 where id=$1;`} - err := q.execOnOneRow(currentUserID, id, value) + err := q.execOnOneRow(currentUser, id, value) return err } @@ -288,9 +279,9 @@ func UpdateUserAutoFavicon(currentUserID string, id string, value bool) error { /* UpdateUserMaxTagInCloud met a jour le nombre d'element du nuage de tag */ -func UpdateUserMaxTagInCloud(currentUserID string, id string, value int8) error { +func UpdateUserMaxTagInCloud(currentUser model.BowUser, id string, value int8) error { q := &query{sql: `update bowuser SET maxTagInCloud=$2 where id=$1;`} - err := q.execOnOneRow(currentUserID, id, value) + err := q.execOnOneRow(currentUser, id, value) return err } @@ -298,9 +289,9 @@ func UpdateUserMaxTagInCloud(currentUserID string, id string, value int8) error /* UpdateUserMaxResult met a jour le nombre d'element affiché dans une page de resultat */ -func UpdateUserMaxResult(currentUserID string, id string, value int8) error { +func UpdateUserMaxResult(currentUser model.BowUser, id string, value int8) error { q := &query{sql: `update bowuser SET maxResult=$2 where id=$1;`} - err := q.execOnOneRow(currentUserID, id, value) + err := q.execOnOneRow(currentUser, id, value) return err } @@ -308,7 +299,7 @@ func UpdateUserMaxResult(currentUserID string, id string, value int8) error { /* ConfirmUserEmail verif et confirme un email */ -func ConfirmUserEmail(currentUserID string, id string, token string) error { +func ConfirmUserEmail(currentUser model.BowUser, id string, token string) error { // le but est de simplement faire passer du statut non confirmer à confirmer un email // si on le retrouve pour l'utilisateur et que le token est le bon. // mais avec le schema de base choisi c'est un poil compliqué @@ -319,7 +310,6 @@ func ConfirmUserEmail(currentUserID string, id string, token string) error { // - on garanti que personne n'a deja cette email, sinon on ne fait rien (il reste en a confirmer) jsonPathToCheckToken := fmt.Sprintf(`$[*].token ? (@ == "%s")`, token) - q := &query{sql: ` with ue as (select jsonb_array_elements(unconfirmedemails) as json from bowuser @@ -336,7 +326,7 @@ func ConfirmUserEmail(currentUserID string, id string, token string) error { from data where b.id=$1 and b.unconfirmedemails @? $3 and (select count(id) from bowuser where emails @> ('{' || data.email || '}')::text[]) = 0;`} - err := q.execOnOneRow(currentUserID, id, token, jsonPathToCheckToken) + err := q.execOnOneRow(currentUser, id, token, jsonPathToCheckToken) return err } diff --git a/pkg/utils/error.go b/pkg/utils/error.go index 6331c8d..99be5d6 100644 --- a/pkg/utils/error.go +++ b/pkg/utils/error.go @@ -6,6 +6,8 @@ import ( "net/http" "runtime" "strings" + + "gitlab.chorem.org/chorem/bow/pkg/model" ) /* @@ -22,14 +24,14 @@ type httpError struct { } func (e *httpError) Error() string { - return fmt.Sprintf(`{"ID": "%s", "CurrentUserID": "%s", "StatusCode": %v, "Msg": %q}`, e.ID, e.CurrentUserID, e.StatusCode, e.Msg) + return fmt.Sprintf(`{"ID": "%s", "currentUser": "%s", "StatusCode": %v, "Msg": %q}`, e.ID, e.CurrentUserID, e.StatusCode, e.Msg) } -func createHTTPError(msg string, currentUserID string, statusCode int) *httpError { +func createHTTPError(msg string, currentUser model.BowUser, statusCode int) *httpError { e := httpError{} e.ID, _ = GenUUID() e.Msg = msg - e.CurrentUserID = currentUserID + e.CurrentUserID = currentUser.ID if statusCode > 0 { e.StatusCode = statusCode } else { @@ -47,24 +49,24 @@ func createHTTPError(msg string, currentUserID string, statusCode int) *httpErro return &e } -func NewHTTPError(msg string, currentUserID string, statusCode int) *httpError { - return createHTTPError(msg, currentUserID, statusCode) +func NewHTTPError(msg string, currentUser model.BowUser, statusCode int) *httpError { + return createHTTPError(msg, currentUser, statusCode) } -func NewHTTPError400(err error, currentUserID string) *httpError { +func NewHTTPError400(err error, currentUser model.BowUser) *httpError { if err, ok := err.(*httpError); ok { return err } - return createHTTPError(fmt.Sprintf("%v", err), currentUserID, 400) + return createHTTPError(fmt.Sprintf("%v", err), currentUser, 400) } -func NewHTTPError500(err error, currentUserID string) *httpError { +func NewHTTPError500(err error, currentUser model.BowUser) *httpError { if err, ok := err.(*httpError); ok { return err } - return createHTTPError(fmt.Sprintf("%v", err), currentUserID, 500) + return createHTTPError(fmt.Sprintf("%v", err), currentUser, 500) } func Is404(err error) bool { diff --git a/pkg/utils/jwt.go b/pkg/utils/jwt.go index a1d8fd5..2dca08e 100644 --- a/pkg/utils/jwt.go +++ b/pkg/utils/jwt.go @@ -1,7 +1,10 @@ package utils import ( + "encoding/json" + "github.com/brianvoe/sjwt" + "gitlab.chorem.org/chorem/bow/pkg/model" ) var secretKey []byte @@ -16,41 +19,63 @@ func JwtInit(key []byte) { /* JwtVerify check token and if valide return user id */ -func JwtVerify(token string) string { +func JwtVerify(token string) (model.BowUser, error) { + var bowUser model.BowUser + // check signature verified := sjwt.Verify(token, secretKey) if !verified { - return "" + return bowUser, NewHTTPError("Can't verify token", bowUser, 401) } // read token claims, err := sjwt.Parse(token) if err != nil { - return "" + return bowUser, NewHTTPError("Can't read token", bowUser, 401) } // check date err = claims.Validate() if err != nil { - return "" + return bowUser, NewHTTPError("Can't validate token", bowUser, 401) } - id, err := claims.GetStr("id") + // bowUserJSON, err := claims.GetStr("user") + // if err != nil { + // return bowUser, NewHTTPError("Can't get user in token", bowUser, 401) + // } + + // err = json.Unmarshal([]byte(bowUserJSON), &bowUser) + // if err != nil { + // return bowUser, NewHTTPError(fmt.Sprintf("Can't unmarshal user in token '%s'", bowUserJSON), bowUser, 401) + // } + + err = claims.ToStruct(&bowUser) if err != nil { - return "" + return bowUser, NewHTTPError("Can't unmarshal token", bowUser, 401) } - return id + return bowUser, nil } /* JwtGenerate generate JWT token from information in parameter */ -func JwtGenerate(id string, userJSON string) string { - claims := sjwt.New() - claims.Set("id", id) - claims.Set("user", userJSON) +func JwtGenerate(userJSON string) (string, error) { + var user model.BowUser + + err := json.Unmarshal([]byte(userJSON), &user) + if err != nil { + return "", NewHTTPError500(err, user) + } + + claims, err := sjwt.ToClaims(user) + if err != nil { + return "", NewHTTPError500(err, user) + } + // claims := sjwt.New() + // claims.Set("user", userJSON) jwt := claims.Generate(secretKey) - return jwt + return jwt, nil } diff --git a/pkg/utils/jwt_test.go b/pkg/utils/jwt_test.go new file mode 100644 index 0000000..51f2970 --- /dev/null +++ b/pkg/utils/jwt_test.go @@ -0,0 +1,21 @@ +package utils + +import ( + "log" + "testing" +) + +func TestJWT(t *testing.T) { + userJson := `{"id":"147e923b-3921-4b17-a88c-43495b477a8a","login":"poussin@codelutin.com","creationdate":"2009-03-13T14:58:36+00:00","maxtagincloud":20,"maxresult":20,"actions":[{"action": "search.add", "prefix": "s=", "suggest": ""}, {"action": "https://search.0h00.pm/yacysearch.html?Enter=&verify=ifexist&contentdom=text&nav=location%2Chosts%2Cauthors%2Cnamespace%2Ctopics%2Cfiletype%2Cprotocol%2Clanguage&startRecord=0&indexof=off&meanCount=5&resource=global&prefermaskfilter=&maximumRec [...] + token, err := JwtGenerate(userJson) + if err != nil { + t.Error("TestJWT", err) + } + + user, err := JwtVerify(token) + if err != nil { + t.Error("TestJWT", err) + } + + log.Println("user", user) +} -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
participants (1)
-
chorem.org scm