From 7dc8f47091f272284a3dc8f908b16e46a1e5eefd Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 25 Oct 2021 16:32:48 +1100 Subject: [PATCH] Start adding user settings --- backend/api/api.go | 50 +++++++++---------- backend/api/user.go | 76 ++++++++++++++++++++++++++++- backend/auth/LoginService.go | 22 ++++++++- backend/middleware/JWTMiddleware.go | 47 ------------------ public/app/main.js | 4 +- public/app/reports.js | 1 - public/app/sidebar.js | 19 ++++++++ public/views/user.html | 1 + 8 files changed, 142 insertions(+), 78 deletions(-) delete mode 100644 backend/middleware/JWTMiddleware.go diff --git a/backend/api/api.go b/backend/api/api.go index 58fe205..e16c31a 100644 --- a/backend/api/api.go +++ b/backend/api/api.go @@ -6,7 +6,6 @@ import ( "github.com/gin-gonic/contrib/gzip" "github.com/gin-gonic/gin" - "github.com/darcys22/godbledger-web/backend/middleware" "github.com/darcys22/godbledger-web/backend/setting" "github.com/sirupsen/logrus" @@ -46,42 +45,43 @@ func register(r *gin.Engine) { // ---- Authenticated Views --------- // Main/Journal Entry Page - r.GET("/", middleware.AuthorizeJWT(), Index) - r.GET("/api/journals", middleware.AuthorizeJWT(), GetJournals) - r.POST("/api/journals", middleware.AuthorizeJWT(), PostJournal) - r.GET("/api/journals/:id", middleware.AuthorizeJWT(), GetJournal) - r.POST("/api/journals/:id", middleware.AuthorizeJWT(), EditJournal) - r.DELETE("/api/journals/:id", middleware.AuthorizeJWT(), DeleteJournal) + r.GET("/", AuthorizeJWT(), Index) + r.GET("/api/journals", AuthorizeJWT(), GetJournals) + r.POST("/api/journals", AuthorizeJWT(), PostJournal) + r.GET("/api/journals/:id", AuthorizeJWT(), GetJournal) + r.POST("/api/journals/:id", AuthorizeJWT(), EditJournal) + r.DELETE("/api/journals/:id", AuthorizeJWT(), DeleteJournal) // Chart of Accounts Page - r.GET("/accounts", middleware.AuthorizeJWT(), Accounts) - r.GET("/api/accounts", middleware.AuthorizeJWT(), GetAccounts) - r.POST("/api/accounts", middleware.AuthorizeJWT(), PostAccount) - r.GET("/api/accounts/:id", middleware.AuthorizeJWT(), GetAccount) - r.DELETE("/api/accounts/:id", middleware.AuthorizeJWT(), DeleteAccount) - r.POST("/api/accounttags", middleware.AuthorizeJWT(), PostAccountTag) - r.DELETE("/api/accounttags/:account/:tagid", middleware.AuthorizeJWT(), DeleteAccountTag) + r.GET("/accounts", AuthorizeJWT(), Accounts) + r.GET("/api/accounts", AuthorizeJWT(), GetAccounts) + r.POST("/api/accounts", AuthorizeJWT(), PostAccount) + r.GET("/api/accounts/:id", AuthorizeJWT(), GetAccount) + r.DELETE("/api/accounts/:id", AuthorizeJWT(), DeleteAccount) + r.POST("/api/accounttags", AuthorizeJWT(), PostAccountTag) + r.DELETE("/api/accounttags/:account/:tagid", AuthorizeJWT(), DeleteAccountTag) // Reconciliation Page - r.GET("/reconcile", middleware.AuthorizeJWT(), Reconcile) - r.GET("/api/reconcile/listexternalaccounts", middleware.AuthorizeJWT(), GetExternalAccountListing) - r.POST("/api/reconcile/listunreconciledtransactions", middleware.AuthorizeJWT(), GetUnreconciledTransactions) + r.GET("/reconcile", AuthorizeJWT(), Reconcile) + r.GET("/api/reconcile/listexternalaccounts", AuthorizeJWT(), GetExternalAccountListing) + r.POST("/api/reconcile/listunreconciledtransactions", AuthorizeJWT(), GetUnreconciledTransactions) // Reports Page - r.GET("/reports", middleware.AuthorizeJWT(), Reports) - r.POST("api/reports/", middleware.AuthorizeJWT(), ReportsResults) + r.GET("/reports", AuthorizeJWT(), Reports) + r.POST("api/reports/", AuthorizeJWT(), ReportsResults) // Modules Page - r.GET("/modules", middleware.AuthorizeJWT(), Modules) + r.GET("/modules", AuthorizeJWT(), Modules) // Users Page - r.GET("/user", middleware.AuthorizeJWT(), User) - r.POST("/changepassword", middleware.AuthorizeJWT(), ChangePassword) - r.POST("/defaultcurrency", middleware.AuthorizeJWT(), DefaultCurrency) + r.GET("/user", AuthorizeJWT(), User) + r.GET("/api/user/settings", AuthorizeJWT(), GetUserSettings) + r.POST("/api/user/changepassword", AuthorizeJWT(), ChangePassword) + r.POST("/api/user/defaultcurrency", AuthorizeJWT(), DefaultCurrency) // Admin Page - r.GET("/admin", middleware.AuthorizeJWT(), Admin) - r.POST("/newuser", middleware.AuthorizeJWT(), NewUser) + r.GET("/admin", AuthorizeJWT(), Admin) + r.POST("/api/newuser", AuthorizeJWT(), NewUser) } diff --git a/backend/api/user.go b/backend/api/user.go index 65e9903..2e9e1bd 100644 --- a/backend/api/user.go +++ b/backend/api/user.go @@ -1,11 +1,85 @@ package api import ( + "net/http" + "net/url" + + "github.com/darcys22/godbledger-web/backend/auth" m "github.com/darcys22/godbledger-web/backend/models" + "github.com/gin-gonic/gin" - "net/http" + "github.com/dgrijalva/jwt-go" + ) +type UserSettings struct { + // Simply the username/email will be displayed in client + Name string `json:"name"` + // Admin or Regular user, will allow for hiding admin screens but server side will also check + Role string `json:"role"` + // Used for date parsing - https://sugarjs.com/docs/#/DateLocales + DateLocale string `json:"datelocale"` + // USD - will be used by client for all currency items + DefaultCurrency string `json:"defaultcurrency"` +} + +func respondWithError(ctx *gin.Context, message interface{}) { + log.Debugf("Error processing JWT: %v", message) + ctx.Abort() + location := url.URL{Path: "/login"} + ctx.Redirect(http.StatusFound, location.RequestURI()) +} + +func AuthorizeJWT() gin.HandlerFunc { + return func(ctx *gin.Context) { + cookie, err := ctx.Request.Cookie("access_token") + if err != nil { + respondWithError(ctx, "Cookie required") + return + } + tokenString := cookie.Value + token, err := auth.JWTAuthService().ValidateToken(tokenString) + if err != nil { + respondWithError(ctx, err) + return + } else { + if token.Valid { + claims := token.Claims.(jwt.MapClaims) + log.Println(claims) + } else { + respondWithError(ctx, "Invalid API token") + return + } + } + ctx.Next() + } +} + +func GetUserSettings(ctx *gin.Context) { + settings := UserSettings{} + cookie, err := ctx.Request.Cookie("access_token") + if err != nil { + respondWithError(ctx, "Cookie required") + return + } + tokenString := cookie.Value + user, err := auth.JWTAuthService().ParseUser(tokenString) + if err != nil { + respondWithError(ctx, "Invalid API token") + return + } else { + settings.Name = user + //TODO sean put actual currency here + settings.DefaultCurrency = "USD" + //TODO sean put actual currency here + settings.DateLocale = "en-AU" + //settings.DateLocale = "en-US" + //TODO sean put actual role here + settings.Role = "Admin" + } + ctx.JSON(200, settings) +} + func ChangePassword(c *gin.Context) { var journal m.PostJournalCommand diff --git a/backend/auth/LoginService.go b/backend/auth/LoginService.go index ddff075..9158223 100644 --- a/backend/auth/LoginService.go +++ b/backend/auth/LoginService.go @@ -42,6 +42,7 @@ func (info *loginInformation) NewUser(email string, password string) bool { type JWTService interface { GenerateToken(email string, isUser bool) string ValidateToken(token string) (*jwt.Token, error) + ParseUser(token string) (string, error) } type authCustomClaims struct { Name string `json:"name"` @@ -89,10 +90,29 @@ func (service *jwtServices) GenerateToken(email string, isUser bool) string { func (service *jwtServices) ValidateToken(encodedToken string) (*jwt.Token, error) { return jwt.Parse(encodedToken, func(token *jwt.Token) (interface{}, error) { if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid { - return nil, fmt.Errorf("Invalid token", token.Header["alg"]) + return nil, fmt.Errorf("Invalid token %v", token.Header["alg"]) } return []byte(service.secretKey), nil }) } + +//type authCustomClaims struct { +func (service *jwtServices) ParseUser(encodedToken string) (string, error) { + token, err := jwt.ParseWithClaims(encodedToken, &authCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid { + return nil, fmt.Errorf("Invalid token %v", token.Header["alg"]) + + } + return []byte(service.secretKey), nil + }) + if err != nil { + return "", err + } + if claims, ok := token.Claims.(*authCustomClaims); ok && token.Valid { + return claims.Name, nil + } else { + return "", fmt.Errorf("Invalid Token") + } +} diff --git a/backend/middleware/JWTMiddleware.go b/backend/middleware/JWTMiddleware.go deleted file mode 100644 index e9336b7..0000000 --- a/backend/middleware/JWTMiddleware.go +++ /dev/null @@ -1,47 +0,0 @@ -package middleware - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/darcys22/godbledger-web/backend/auth" - - "github.com/dgrijalva/jwt-go" - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -var log = logrus.WithField("prefix", "JWTMiddleware") - -func respondWithError(ctx *gin.Context, message interface{}) { - log.Debugf("Error processing JWT: ", message) - ctx.Abort() - location := url.URL{Path: "/login"} - ctx.Redirect(http.StatusFound, location.RequestURI()) -} - -func AuthorizeJWT() gin.HandlerFunc { - return func(ctx *gin.Context) { - cookie, err := ctx.Request.Cookie("access_token") - if err != nil { - respondWithError(ctx, "Cookie required") - return - } - tokenString := cookie.Value - token, err := auth.JWTAuthService().ValidateToken(tokenString) - if err != nil { - respondWithError(ctx, err) - return - } else { - if token.Valid { - claims := token.Claims.(jwt.MapClaims) - fmt.Println(claims) - } else { - respondWithError(ctx, "Invalid API token") - return - } - } - ctx.Next() - } -} diff --git a/public/app/main.js b/public/app/main.js index bcac111..04442eb 100644 --- a/public/app/main.js +++ b/public/app/main.js @@ -2,8 +2,7 @@ class LineItem { constructor() { this._date = moment().format(); this._description = ""; - //TODO sean this needs to be editable - this._currency= "USD"; + this._currency= window.user.defaultcurrency; this._account = ""; this._amount = 0; } @@ -632,7 +631,6 @@ const copyToClipboard = str => { }; function main() { - Date.setLocale("en-AU"); $('#addJournal')[0].reset(); updateTotal() $('#saveJournalButton').prop('disabled', true); diff --git a/public/app/reports.js b/public/app/reports.js index 450908b..53ce27b 100644 --- a/public/app/reports.js +++ b/public/app/reports.js @@ -307,6 +307,5 @@ function dateQuickSelect(rangeType) { } function main() { - Date.setLocale("en-AU"); } main(); diff --git a/public/app/sidebar.js b/public/app/sidebar.js index e70cb95..844f2ad 100644 --- a/public/app/sidebar.js +++ b/public/app/sidebar.js @@ -2,6 +2,7 @@ let sidebar = document.querySelector(".sidebar"); let closeBtn = document.querySelector("#btn"); let searchBtn = document.querySelector(".bx-search"); + closeBtn.addEventListener("click", ()=>{ sidebar.classList.toggle("open"); menuBtnChange();//calling the function(optional) @@ -21,3 +22,21 @@ function menuBtnChange() { } } +fetch('/api/user/settings', { + method: 'GET', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + } +}) +.then(response => response.json()) +.then(data => { + let name = document.querySelector(".name"); + let role = document.querySelector(".job"); + window.user = data + name.innerText = data.name + role.innerText = data.role + if (typeof Date.setLocale !== 'undefined') { + Date.setLocale(data.datelocale); + } +}) + diff --git a/public/views/user.html b/public/views/user.html index 1b878cc..01aff87 100644 --- a/public/views/user.html +++ b/public/views/user.html @@ -99,6 +99,7 @@
Default Date Locale
console.log("lol fail") }); }); + {{ template "footer.html" .}}