generated from datumforge/go-template
-
Notifications
You must be signed in to change notification settings - Fork 7
/
switch.go
136 lines (107 loc) · 4.31 KB
/
switch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package handlers
import (
"net/http"
echo "github.com/datumforge/echox"
"github.com/golang-jwt/jwt/v5"
ph "github.com/posthog/posthog-go"
"github.com/datumforge/datum/internal/ent/generated"
"github.com/datumforge/datum/internal/ent/generated/privacy"
"github.com/datumforge/datum/pkg/auth"
"github.com/datumforge/datum/pkg/rout"
"github.com/datumforge/datum/pkg/sessions"
"github.com/datumforge/datum/pkg/tokens"
)
// SwitchOrganizationRequest contains the target organization ID being switched to
type SwitchOrganizationRequest struct {
TargetOrganizationID string `json:"target_organization_id"`
}
// SwitchOrganizationReply holds the new authentication and session information for the user for the new organization
type SwitchOrganizationReply struct {
rout.Reply
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Session string `json:"session"`
}
// SwitchHandler is responsible for handling requests to the `/switch` endpoint, and changing the user's logged in organization context
func (h *Handler) SwitchHandler(ctx echo.Context) error {
var req SwitchOrganizationRequest
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse(err))
}
reqCtx := ctx.Request().Context()
userID, err := auth.GetUserIDFromContext(reqCtx)
if err != nil {
h.Logger.Errorw("unable to get user id from context", "error", err)
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse(err))
}
// get user from database by subject
user, err := h.getUserDetailsByID(reqCtx, userID)
if err != nil {
h.Logger.Errorw("unable to get user by subject", "error", err)
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse(err))
}
orgID, err := auth.GetOrganizationIDFromContext(reqCtx)
if err != nil {
h.Logger.Errorw("unable to get organization id from context", "error", err)
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse(err))
}
// ensure the user is not already in the target organization
if orgID == req.TargetOrganizationID {
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse("already switched to organization"))
}
// ensure user is already a member of the destination organization
if err := h.confirmOrgMembership(reqCtx, userID, req.TargetOrganizationID); err != nil {
return ctx.JSON(http.StatusBadRequest, rout.ErrorResponse(err))
}
// get the target organization
orgGetCtx := privacy.DecisionContext(reqCtx, privacy.Allow)
org, err := h.getOrgByID(orgGetCtx, req.TargetOrganizationID)
if err != nil {
h.Logger.Errorw("unable to get target organization by id", "error", err)
}
// create new claims for the user
newClaims := switchClaims(user, org.MappingID)
// create a new token pair for the user
access, refresh, err := h.TM.CreateTokenPair(newClaims)
if err != nil {
return ctx.JSON(http.StatusInternalServerError, rout.ErrorResponse(err))
}
// set cookies on request with the access and refresh token
auth.SetAuthCookies(ctx.Response().Writer, access, refresh)
// set sessions in response
if err := h.SessionConfig.CreateAndStoreSession(ctx, user.ID); err != nil {
h.Logger.Errorw("unable to save session", "error", err)
return ctx.JSON(http.StatusInternalServerError, rout.ErrorResponse(err))
}
// return the session value for the UI to use
session, err := sessions.SessionToken(ctx.Request().Context())
if err != nil {
return ctx.JSON(http.StatusInternalServerError, rout.ErrorResponse(err))
}
// track the organization switch event
props := ph.NewProperties().
Set("user_id", user.ID).
Set("email", user.Email).
Set("target_organization_id", org.ID).
Set("auth_provider", user.AuthProvider).
Set("previous_organization_id", orgID)
h.AnalyticsClient.Event("organization_switched", props)
// set the out attributes we send back to the client only on success
out := &SwitchOrganizationReply{
Reply: rout.Reply{Success: true},
AccessToken: access,
RefreshToken: refresh,
Session: session,
}
return ctx.JSON(http.StatusOK, out)
}
// switchClaims creates a new set of claims for the user based on the target organization and returns them
func switchClaims(u *generated.User, targetOrgMappingID string) *tokens.Claims {
return &tokens.Claims{
RegisteredClaims: jwt.RegisteredClaims{
Subject: u.MappingID,
},
UserID: u.MappingID,
OrgID: targetOrgMappingID,
}
}