diff --git a/cmd/web-app/handlers/signup.go b/cmd/web-app/handlers/signup.go
index 023ec4a..37e0024 100644
--- a/cmd/web-app/handlers/signup.go
+++ b/cmd/web-app/handlers/signup.go
@@ -74,7 +74,7 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque
}
// Add the token to the users session.
- err = handleSessionToken(ctx, w, r, token)
+ err = handleSessionToken(ctx, h.MasterDB, w, r, token)
if err != nil {
return err
}
diff --git a/cmd/web-app/handlers/user.go b/cmd/web-app/handlers/user.go
index d398a64..b60003e 100644
--- a/cmd/web-app/handlers/user.go
+++ b/cmd/web-app/handlers/user.go
@@ -78,7 +78,7 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request
token, err := user.Authenticate(ctx, h.MasterDB, h.Authenticator, req.Email, req.Password, sessionTTL, ctxValues.Now)
if err != nil {
switch errors.Cause(err) {
- case account.ErrForbidden:
+ case user.ErrForbidden:
return web.RespondError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
default:
if verr, ok := weberror.NewValidationError(ctx, err); ok {
@@ -91,7 +91,7 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request
}
// Add the token to the users session.
- err = handleSessionToken(ctx, w, r, token)
+ err = handleSessionToken(ctx, h.MasterDB, w, r, token)
if err != nil {
return err
}
@@ -117,11 +117,21 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request
}
// handleSessionToken persists the access token to the session for request authentication.
-func handleSessionToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token user.Token) error {
+func handleSessionToken(ctx context.Context, db *sqlx.DB, w http.ResponseWriter, r *http.Request, token user.Token) error {
if token.AccessToken == "" {
return errors.New("accessToken is required.")
}
+ usr, err := user.Read(ctx, auth.Claims{}, db, token.UserID, false )
+ if err != nil {
+ return err
+ }
+
+ acc, err := account.Read(ctx, auth.Claims{},db, token.AccountID, false )
+ if err != nil {
+ return err
+ }
+
sess := webcontext.ContextSession(ctx)
if sess.IsNew {
@@ -134,7 +144,7 @@ func handleSessionToken(ctx context.Context, w http.ResponseWriter, r *http.Requ
HttpOnly: false,
}
- sess = webcontext.SessionWithAccessToken(sess, token.AccessToken)
+ sess = webcontext.SessionInit(sess, token.AccessToken, usr.Response(ctx), acc.Response(ctx))
if err := sess.Save(r, w); err != nil {
return err
@@ -149,7 +159,7 @@ func (h *User) Logout(ctx context.Context, w http.ResponseWriter, r *http.Reques
sess := webcontext.ContextSession(ctx)
// Set the access token to empty to logout the user.
- sess = webcontext.SessionWithAccessToken(sess, "")
+ sess = webcontext.SessionDestroy(sess)
if err := sess.Save(r, w); err != nil {
return err
@@ -293,7 +303,7 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.
}
// Add the token to the users session.
- err = handleSessionToken(ctx, w, r, token)
+ err = handleSessionToken(ctx, h.MasterDB, w, r, token)
if err != nil {
return err
}
diff --git a/cmd/web-app/main.go b/cmd/web-app/main.go
index 01f13ac..b10069d 100644
--- a/cmd/web-app/main.go
+++ b/cmd/web-app/main.go
@@ -6,7 +6,9 @@ import (
"encoding/json"
"expvar"
"fmt"
+ "geeks-accelerator/oss/saas-starter-kit/internal/account"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
+ "geeks-accelerator/oss/saas-starter-kit/internal/user"
"gopkg.in/gomail.v2"
"html/template"
"log"
@@ -674,6 +676,23 @@ func main() {
return fmt.Sprintf("%+v", err)
},
+ "ContextUser": func(ctx context.Context) *user.UserResponse {
+ sess := webcontext.ContextSession(ctx)
+ v, _ := webcontext.SessionUser(sess)
+
+ if u, ok := v.(*user.UserResponse); ok {
+ return u
+ }
+ return nil
+ },
+ "ContextAccount": func(ctx context.Context) *account.AccountResponse {
+ sess := webcontext.ContextSession(ctx)
+ v, _ := webcontext.SessionAccount(sess)
+ if acc, ok := v.(*account.AccountResponse); ok {
+ return acc
+ }
+ return nil
+ },
}
imgUrlFormatter := staticUrlFormatter
diff --git a/cmd/web-app/templates/partials/topbar.tmpl b/cmd/web-app/templates/partials/topbar.tmpl
index aab71d2..fb37835 100644
--- a/cmd/web-app/templates/partials/topbar.tmpl
+++ b/cmd/web-app/templates/partials/topbar.tmpl
@@ -154,25 +154,52 @@
- Valerie Luna
-
+
+ {{ $user := ContextUser $._Ctx }}
+ {{ if $user }}
+ {{ $user.Name }}
+
+ {{ else }}
+ Space Cadet
+
+ {{ end }}
+
-
+
- Profile
+ My Profile
-
+
+ {{ if HasRole $._Ctx "admin" }}
+
+
+ Account Settings
+
+
+
+
+ Manage Users
+
+
+
+ Invite User
+
+ {{ else }}
+
+
+ Account
+
+ {{ end }}
+
+
- Settings
-
-
-
- Activity Log
+ Support
+
-
+
Logout
diff --git a/internal/platform/web/models.go b/internal/platform/web/models.go
index 1ccbf3b..7896e90 100644
--- a/internal/platform/web/models.go
+++ b/internal/platform/web/models.go
@@ -2,6 +2,7 @@ package web
import (
"context"
+ "crypto/md5"
"fmt"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
"github.com/dustin/go-humanize"
@@ -97,3 +98,17 @@ func EnumValueTitle(v string) string {
v = strings.Replace(v, "_", " ", -1)
return strings.Title(v)
}
+
+type GravatarResponse struct {
+ Small string `json:"small" example:"https://www.gravatar.com/avatar/xy7.jpg?s=30"`
+ Medium string `json:"medium" example:"https://www.gravatar.com/avatar/xy7.jpg?s=80"`
+}
+
+func NewGravatarResponse(ctx context.Context, email string) GravatarResponse {
+ u := fmt.Sprintf("https://www.gravatar.com/avatar/%x.jpg?s=", md5.Sum([]byte(strings.ToLower(email))))
+
+ return GravatarResponse{
+ Small: u+"30",
+ Medium: u+"80",
+ }
+}
diff --git a/internal/platform/web/template-renderer/template_renderer.go b/internal/platform/web/template-renderer/template_renderer.go
index dcdc395..8cb06dc 100644
--- a/internal/platform/web/template-renderer/template_renderer.go
+++ b/internal/platform/web/template-renderer/template_renderer.go
@@ -3,9 +3,6 @@ package template_renderer
import (
"context"
"fmt"
- "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
- "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
- "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
"html/template"
"math"
"net/http"
@@ -15,7 +12,10 @@ import (
"reflect"
"strings"
+ "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
+ "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
+ "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
"github.com/pkg/errors"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
@@ -307,9 +307,9 @@ func (r *TemplateRenderer) Render(ctx context.Context, w http.ResponseWriter, re
// Specific new data map for render to allow values to be overwritten on a request
// basis.
// append the global key/pairs
- renderData := r.globalViewData
- if renderData == nil {
- renderData = make(map[string]interface{})
+ renderData := make(map[string]interface{}, len(r.globalViewData))
+ for k, v := range r.globalViewData {
+ renderData[k] = v
}
// Add Request URL to render data
diff --git a/internal/platform/web/webcontext/session.go b/internal/platform/web/webcontext/session.go
index a403973..d205a3b 100644
--- a/internal/platform/web/webcontext/session.go
+++ b/internal/platform/web/webcontext/session.go
@@ -15,6 +15,12 @@ const KeySession ctxKeySession = 1
// KeyAccessToken is used to store the access token for the user in their session.
const KeyAccessToken = "AccessToken"
+// KeyUser is used to store the user in the session.
+const KeyUser = "User"
+
+// KeyAccount is used to store the account in the session.
+const KeyAccount = "Account"
+
// ContextWithSession appends a universal translator to a context.
func ContextWithSession(ctx context.Context, session *sessions.Session) context.Context {
return context.WithValue(ctx, KeySession, session)
@@ -39,7 +45,23 @@ func SessionAccessToken(session *sessions.Session) (string, bool) {
return "", false
}
-func SessionWithAccessToken(session *sessions.Session, accessToken string) *sessions.Session {
+func SessionUser(session *sessions.Session) ( interface{}, bool) {
+ if sv, ok := session.Values[KeyUser]; ok && sv != nil {
+ return sv, true
+ }
+
+ return nil, false
+}
+
+func SessionAccount(session *sessions.Session) (interface{}, bool) {
+ if sv, ok := session.Values[KeyAccount]; ok && sv != nil {
+ return sv, true
+ }
+
+ return nil, false
+}
+
+func SessionInit(session *sessions.Session, accessToken string, usr interface{}, acc interface{}) *sessions.Session {
if accessToken != "" {
session.Values[KeyAccessToken] = accessToken
@@ -47,5 +69,27 @@ func SessionWithAccessToken(session *sessions.Session, accessToken string) *sess
delete(session.Values, KeyAccessToken)
}
+ if usr != nil {
+ session.Values[KeyUser] = usr
+ } else {
+ delete(session.Values, KeyUser)
+ }
+
+ if acc != nil {
+ session.Values[KeyAccount] = acc
+ } else {
+ delete(session.Values, KeyAccount)
+ }
+
return session
}
+
+func SessionDestroy(session *sessions.Session) *sessions.Session {
+
+ delete(session.Values, KeyAccessToken)
+ delete(session.Values, KeyUser)
+ delete(session.Values, KeyAccount)
+
+ return session
+}
+
diff --git a/internal/user/auth.go b/internal/user/auth.go
index 19742c2..c2fd5f9 100644
--- a/internal/user/auth.go
+++ b/internal/user/auth.go
@@ -287,6 +287,8 @@ func generateToken(ctx context.Context, dbConn *sqlx.DB, tknGen TokenGenerator,
AccessToken: tknStr,
TokenType: "Bearer",
claims: claims,
+ UserID: claims.Subject,
+ AccountID: claims.Audience,
}
if expires.Seconds() > 0 {
diff --git a/internal/user/models.go b/internal/user/models.go
index c0995c7..270f0a8 100644
--- a/internal/user/models.go
+++ b/internal/user/models.go
@@ -35,6 +35,7 @@ type UserResponse struct {
CreatedAt web.TimeResponse `json:"created_at"` // CreatedAt contains multiple format options for display.
UpdatedAt web.TimeResponse `json:"updated_at"` // UpdatedAt contains multiple format options for display.
ArchivedAt *web.TimeResponse `json:"archived_at,omitempty"` // ArchivedAt contains multiple format options for display.
+ Gravatar web.GravatarResponse `json:"gravatar"`
}
// Response transforms User and UserResponse that is used for display.
@@ -52,6 +53,7 @@ func (m *User) Response(ctx context.Context) *UserResponse {
Timezone: m.Timezone,
CreatedAt: web.NewTimeResponse(ctx, m.CreatedAt),
UpdatedAt: web.NewTimeResponse(ctx, m.UpdatedAt),
+ Gravatar: web.NewGravatarResponse(ctx, m.Email),
}
if m.ArchivedAt != nil && !m.ArchivedAt.Time.IsZero() {
@@ -164,4 +166,8 @@ type Token struct {
TTL time.Duration `json:"ttl,omitempty"`
// contains filtered or unexported fields
claims auth.Claims `json:"-"`
+ // UserId is the ID of the user authenticated.
+ UserID string `json:"user_id" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
+ // AccountID is the ID of the account for the user authenticated.
+ AccountID string `json:"account_id"example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"`
}