|
|
@@ -0,0 +1,132 @@
|
|
|
+package user
|
|
|
+
|
|
|
+import (
|
|
|
+ "errors"
|
|
|
+ "net/http"
|
|
|
+
|
|
|
+ "github.com/porter-dev/porter/internal/analytics"
|
|
|
+
|
|
|
+ "github.com/porter-dev/porter/internal/telemetry"
|
|
|
+
|
|
|
+ "gorm.io/gorm"
|
|
|
+
|
|
|
+ "github.com/porter-dev/porter/api/server/shared/apierrors"
|
|
|
+ "github.com/porter-dev/porter/internal/models"
|
|
|
+
|
|
|
+ "github.com/porter-dev/porter/api/server/handlers"
|
|
|
+ "github.com/porter-dev/porter/api/server/shared"
|
|
|
+ "github.com/porter-dev/porter/api/server/shared/config"
|
|
|
+)
|
|
|
+
|
|
|
+// OryUserCreateHandler is the handler for user creation triggered by an ory action
|
|
|
+type OryUserCreateHandler struct {
|
|
|
+ handlers.PorterHandlerReadWriter
|
|
|
+}
|
|
|
+
|
|
|
+// NewOryUserCreateHandler generates a new OryUserCreateHandler
|
|
|
+func NewOryUserCreateHandler(
|
|
|
+ config *config.Config,
|
|
|
+ decoderValidator shared.RequestDecoderValidator,
|
|
|
+ writer shared.ResultWriter,
|
|
|
+) *OryUserCreateHandler {
|
|
|
+ return &OryUserCreateHandler{
|
|
|
+ PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// CreateOryUserRequest is the expected request body for user creation triggered by an ory action
|
|
|
+type CreateOryUserRequest struct {
|
|
|
+ OryId string `json:"ory_id"`
|
|
|
+ Email string `json:"email"`
|
|
|
+ Referral string `json:"referral"`
|
|
|
+}
|
|
|
+
|
|
|
+// ServeHTTP handles the user creation triggered by an ory action
|
|
|
+func (u *OryUserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
+ ctx, span := telemetry.NewSpan(r.Context(), "serve-create-ory-user")
|
|
|
+ defer span.End()
|
|
|
+
|
|
|
+ // this endpoint is not authenticated through middleware; instead, we check
|
|
|
+ // for the presence of an ory action cookie that matches env
|
|
|
+ oryActionCookie, err := r.Cookie("ory_action")
|
|
|
+ if err != nil {
|
|
|
+ err = telemetry.Error(ctx, span, err, "invalid ory action cookie")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if oryActionCookie.Value != u.Config().OryActionKey {
|
|
|
+ err = telemetry.Error(ctx, span, nil, "cookie does not match")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ request := &CreateOryUserRequest{}
|
|
|
+ ok := u.DecodeAndValidate(w, r, request)
|
|
|
+ if !ok {
|
|
|
+ err = telemetry.Error(ctx, span, nil, "invalid request")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ telemetry.WithAttributes(span,
|
|
|
+ telemetry.AttributeKV{Key: "email", Value: request.Email},
|
|
|
+ telemetry.AttributeKV{Key: "ory-id", Value: request.OryId},
|
|
|
+ telemetry.AttributeKV{Key: "referral", Value: request.Referral},
|
|
|
+ )
|
|
|
+
|
|
|
+ if request.Email == "" {
|
|
|
+ err = telemetry.Error(ctx, span, nil, "email is required")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if request.OryId == "" {
|
|
|
+ err = telemetry.Error(ctx, span, nil, "ory_id is required")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ user := &models.User{
|
|
|
+ Model: gorm.Model{},
|
|
|
+ Email: request.Email,
|
|
|
+ EmailVerified: false,
|
|
|
+ AuthProvider: models.AuthProvider_Ory,
|
|
|
+ ExternalId: request.OryId,
|
|
|
+ }
|
|
|
+
|
|
|
+ existingUser, err := u.Repo().User().ReadUserByEmail(user.Email)
|
|
|
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
+ err = telemetry.Error(ctx, span, err, "error reading user by email")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if existingUser == nil || existingUser.ID == 0 {
|
|
|
+ user, err = u.Repo().User().CreateUser(user)
|
|
|
+ if err != nil {
|
|
|
+ err = telemetry.Error(ctx, span, err, "error creating user")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ _ = u.Config().AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))
|
|
|
+
|
|
|
+ _ = u.Config().AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
|
|
|
+ UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
|
|
|
+ Email: user.Email,
|
|
|
+ FirstName: user.FirstName,
|
|
|
+ LastName: user.LastName,
|
|
|
+ CompanyName: user.CompanyName,
|
|
|
+ ReferralMethod: request.Referral,
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ existingUser.AuthProvider = models.AuthProvider_Ory
|
|
|
+ existingUser.ExternalId = request.OryId
|
|
|
+ _, err = u.Repo().User().UpdateUser(existingUser)
|
|
|
+ if err != nil {
|
|
|
+ err = telemetry.Error(ctx, span, err, "error updating user")
|
|
|
+ u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|