Skip to content

Commit

Permalink
Merge pull request #504 from flanksource/clerk-ext-id
Browse files Browse the repository at this point in the history
chore: use external_id to store clerk id
  • Loading branch information
moshloop authored Aug 22, 2023
2 parents 4829d40 + 62a465f commit 930cf0e
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 67 deletions.
1 change: 1 addition & 0 deletions api/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Person struct {
Email string `json:"email,omitempty"`
Avatar string `json:"avatar,omitempty"`
Properties PersonProperties `json:"properties,omitempty"`
ExternalID string `json:"external_id,omitempty"`
}

func (person Person) TableName() string {
Expand Down
81 changes: 34 additions & 47 deletions auth/clerk_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"github.com/flanksource/commons/logger"
"github.com/flanksource/incident-commander/api"
"github.com/flanksource/incident-commander/db"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/patrickmn/go-cache"
"gorm.io/gorm"
)

const (
Expand Down Expand Up @@ -50,7 +50,7 @@ func (h ClerkHandler) Session(next echo.HandlerFunc) echo.HandlerFunc {
sessionToken := c.Request().Header.Get(echo.HeaderAuthorization)
sessionToken = strings.TrimPrefix(sessionToken, "Bearer ")
if sessionToken == "" {
// Check for `_session` cookie
// Check for `__session` cookie
sessionTokenCookie, err := c.Request().Cookie(clerkSessionCookie)
if err != nil {
// Cookie not found
Expand All @@ -59,82 +59,77 @@ func (h ClerkHandler) Session(next echo.HandlerFunc) echo.HandlerFunc {
sessionToken = sessionTokenCookie.Value
}

user, sessID, err := h.getUser(sessionToken)
ctx := c.(*api.Context)
user, sessID, err := h.getUser(ctx, sessionToken)
if err != nil {
logger.Errorf("Error fetching user from clerk: %v", err)
return c.String(http.StatusUnauthorized, "Unauthorized")
}

ctx := c.(*api.Context)
if user.ExternalID == nil {
user, err = h.createDBUser(ctx, user)
if err != nil {
logger.Errorf("Error creating user in database from clerk: %v", err)
return c.String(http.StatusUnauthorized, "Unauthorized")
}
// Clear user from cache so that new metadata is used
h.userCache.Delete(sessID)
}

token, err := h.getDBToken(sessID, *user.ExternalID)
token, err := h.getDBToken(sessID, user.ID.String())
if err != nil {
logger.Errorf("Error generating JWT Token: %v", err)
return c.String(http.StatusUnauthorized, "Unauthorized")
}

c.Request().Header.Set(echo.HeaderAuthorization, fmt.Sprintf("Bearer %s", token))
c.Request().Header.Set(UserIDHeaderKey, *user.ExternalID)
c.Request().Header.Set(UserIDHeaderKey, user.ID.String())
return next(c)
}
}

func (h *ClerkHandler) generateDBToken(id string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"role": DefaultPostgrestRole,
"id": id,
})
return token.SignedString([]byte(h.dbJwtSecret))
}

func (h *ClerkHandler) getDBToken(sessionID, userID string) (string, error) {
cacheKey := sessionID + userID
if token, exists := h.tokenCache.Get(cacheKey); exists {
return token.(string), nil
}
// Adding Authorization Token for PostgREST
token, err := h.generateDBToken(userID)
token, err := generateDBToken(h.dbJwtSecret, userID)
if err != nil {
return "", err
}
h.tokenCache.SetDefault(cacheKey, token)
return token, nil
}

func (h *ClerkHandler) getUser(sessionToken string) (*clerk.User, string, error) {
func (h *ClerkHandler) getUser(ctx *api.Context, sessionToken string) (*api.Person, string, error) {
sessClaims, err := h.client.VerifyToken(sessionToken)
if err != nil {
return nil, "", err
}

cacheKey := sessClaims.SessionID
if user, exists := h.userCache.Get(cacheKey); exists {
return user.(*clerk.User), sessClaims.SessionID, nil
return user.(*api.Person), sessClaims.SessionID, nil
}

user, err := h.client.Users().Read(sessClaims.Claims.Subject)
clerkUser, err := h.client.Users().Read(sessClaims.Claims.Subject)
if err != nil {
return nil, "", err
}
h.userCache.SetDefault(cacheKey, user)
return user, sessClaims.SessionID, nil

dbUser, err := h.createDBUserIfNotExists(ctx, clerkUser)
if err != nil {
return nil, "", err
}
h.userCache.SetDefault(cacheKey, &dbUser)
return &dbUser, sessClaims.SessionID, nil
}

func (h *ClerkHandler) createDBUser(ctx *api.Context, user *clerk.User) (*clerk.User, error) {
if user.ExternalID != nil {
return user, nil
func (h *ClerkHandler) createDBUserIfNotExists(ctx *api.Context, user *clerk.User) (api.Person, error) {
existingUser, err := db.GetUserByExternalID(ctx, user.ID)
if err == nil {
// User with the given clerk ID exists
return existingUser, nil
}

if err != gorm.ErrRecordNotFound {
// Return if any other error, we only want to create the user
return api.Person{}, err
}

if user.PrimaryEmailAddressID == nil {
return nil, fmt.Errorf("clerk.user[%s] has no primary email", user.ID)
return api.Person{}, fmt.Errorf("clerk.user[%s] has no primary email", user.ID)
}

var email string
Expand All @@ -152,19 +147,11 @@ func (h *ClerkHandler) createDBUser(ctx *api.Context, user *clerk.User) (*clerk.
if user.LastName != nil {
name = append(name, *user.LastName)
}
person := api.Person{
Name: strings.Join(name, " "),
Email: email,
}

dbUser, err := db.GetOrCreateUser(ctx, person)
if err != nil {
return nil, err
}

id := dbUser.ID.String()
userUpdateParams := clerk.UpdateUser{
ExternalID: &id,
newUser := api.Person{
Name: strings.Join(name, " "),
Email: email,
ExternalID: user.ID,
}
return h.client.Users().Update(user.ID, &userUpdateParams)
return db.CreateUser(ctx, newUser)
}
11 changes: 1 addition & 10 deletions auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/flanksource/commons/rand"
"github.com/flanksource/commons/utils"
"github.com/flanksource/duty/models"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
client "github.com/ory/client-go"
Expand Down Expand Up @@ -260,21 +259,13 @@ func (k *kratosMiddleware) kratosLogin(ctx context.Context, username, password s
return &login.Session, nil
}

func (k *kratosMiddleware) generateDBToken(id string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"role": DefaultPostgrestRole,
"id": id,
})
return token.SignedString([]byte(k.jwtSecret))
}

func (k *kratosMiddleware) getDBToken(sessionID, userID string) (string, error) {
cacheKey := sessionID + userID
if token, exists := k.tokenCache.Get(cacheKey); exists {
return token.(string), nil
}
// Adding Authorization Token for PostgREST
token, err := k.generateDBToken(userID)
token, err := generateDBToken(k.jwtSecret, userID)
if err != nil {
return "", err
}
Expand Down
11 changes: 11 additions & 0 deletions auth/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package auth

import "github.com/golang-jwt/jwt/v4"

func generateDBToken(secret, id string) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"role": DefaultPostgrestRole,
"id": id,
})
return token.SignedString([]byte(secret))
}
15 changes: 8 additions & 7 deletions db/people.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ func GetUserByID(ctx *api.Context, id string) (api.Person, error) {
return user, err
}

func GetOrCreateUser(ctx *api.Context, user api.Person) (api.Person, error) {
if err := ctx.DB().Table("people").Where("email = ?", user.Email).Find(&user).Error; err != nil {
return api.Person{}, err
}
if user.ID != uuid.Nil {
return user, nil
}
func GetUserByExternalID(ctx *api.Context, id string) (api.Person, error) {
var user api.Person
err := ctx.DB().Table("people").Where("external_id = ?", id).First(&user).Error
return user, err
}

// CreateUser creates a new user and returns a copy
func CreateUser(ctx *api.Context, user api.Person) (api.Person, error) {
err := ctx.DB().Table("people").Create(&user).Error
return user, err
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/containrrr/shoutrrr v0.7.1
github.com/fergusstrange/embedded-postgres v1.23.0
github.com/flanksource/commons v1.11.0
github.com/flanksource/duty v1.0.156
github.com/flanksource/duty v1.0.157
github.com/flanksource/kopper v1.0.6
github.com/google/cel-go v0.17.2
github.com/google/go-cmp v0.5.9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,8 @@ github.com/fergusstrange/embedded-postgres v1.23.0 h1:ZYRD89nammxQDWDi6taJE2CYjD
github.com/fergusstrange/embedded-postgres v1.23.0/go.mod h1:wL562t1V+iuFwq0UcgMi2e9rp8CROY9wxWZEfP8Y874=
github.com/flanksource/commons v1.11.0 h1:ThP3hnX4Xh4thxVl2GjQ92WvQ93jq5VqzJh46jbW23A=
github.com/flanksource/commons v1.11.0/go.mod h1:zYEhi6E2+diQ+loVcROUHo/Bgv+Tn61W2NYmrb5MgVI=
github.com/flanksource/duty v1.0.156 h1:d/wcXUm3nP3bOJDnNPEWa0G24HjrxlR+GUHIY78B6s0=
github.com/flanksource/duty v1.0.156/go.mod h1:RJ/kcZ7dbL8/52tem757szVIA3IomS8bOAZIK0xb4rk=
github.com/flanksource/duty v1.0.157 h1:WxOxI+3NGgMXOv6G4t6GXalZgYhIiORBs06FYqg/CLw=
github.com/flanksource/duty v1.0.157/go.mod h1:RJ/kcZ7dbL8/52tem757szVIA3IomS8bOAZIK0xb4rk=
github.com/flanksource/gomplate/v3 v3.20.4/go.mod h1:27BNWhzzSjDed1z8YShO6W+z6G9oZXuxfNFGd/iGSdc=
github.com/flanksource/gomplate/v3 v3.20.9 h1:I3H/l1FUDepe6IuG8Nj51QNX9ocdU2EGL4GWz31sZdk=
github.com/flanksource/gomplate/v3 v3.20.9/go.mod h1:1N1aptaAo0XUaGsyU5CWiwn9GMRpbIKX1AdsypfmZYo=
Expand Down

0 comments on commit 930cf0e

Please sign in to comment.