1419 lines
48 KiB
Go
1419 lines
48 KiB
Go
package connect_v1
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver
|
|
// implementations
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
// Code generated by github.com/99designs/gqlgen version v0.17.84
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/99designs/gqlgen/graphql"
|
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
"go.gearno.de/kit/log"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/iam"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/securecookie"
|
|
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
|
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
|
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
|
)
|
|
|
|
// Memberships is the resolver for the memberships field.
|
|
func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.MembershipConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.MembershipOrderField]{
|
|
Field: coredata.MembershipOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.MembershipOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListMemberships(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list memberships", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewMembershipConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// PendingInvitations is the resolver for the pendingInvitations field.
|
|
func (r *identityResolver) PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.InvitationConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
|
|
Field: coredata.InvitationOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list pending invitations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewInvitationConnection(page, r, obj.ID, nil), nil
|
|
}
|
|
|
|
// Sessions is the resolver for the sessions field.
|
|
func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.SessionConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.SessionOrderField]{
|
|
Field: coredata.SessionOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.SessionOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListSessions(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list sessions", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewSessionConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// PersonalAPIKeys is the resolver for the personalAPIKeys field.
|
|
func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.PersonalAPIKeyConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.PersonalAPIKeyOrderField]{
|
|
Field: coredata.PersonalAPIKeyOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list personal api keys", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string, id gid.GID) (bool, error) {
|
|
|
|
fmt.Printf("action: %s, id: %s\n", action, id.String())
|
|
fmt.Printf("obj: %+v\n", obj)
|
|
|
|
err := r.iam.Authorizer.Authorize(
|
|
ctx,
|
|
iam.AuthorizeParams{
|
|
Principal: obj.ID,
|
|
Resource: id,
|
|
Action: action,
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
var errInsufficientPermissions *iam.ErrInsufficientPermissions
|
|
if errors.As(err, &errInsufficientPermissions) {
|
|
graphql.AddError(
|
|
ctx,
|
|
&gqlerror.Error{
|
|
Path: graphql.GetPath(ctx),
|
|
Message: err.Error(),
|
|
Extensions: map[string]interface{}{
|
|
"code": "UNAUTHORIZED",
|
|
},
|
|
},
|
|
)
|
|
|
|
return false, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err))
|
|
return false, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *invitationResolver) Organization(ctx context.Context, obj *types.Invitation) (*types.Organization, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Organization{
|
|
ID: obj.Organization.ID,
|
|
}, nil
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.GetOrganizationForInvitation(ctx, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get organization for invitation", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := r.iam.OrganizationService.CountInvitations(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count invitations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
return &count, nil
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountPendingInvitations(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count invitations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
// Identity is the resolver for the identity field.
|
|
func (r *membershipResolver) Identity(ctx context.Context, obj *types.Membership) (*types.Identity, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Identity{
|
|
ID: obj.Identity.ID,
|
|
}, nil
|
|
}
|
|
|
|
identity, err := r.iam.AccountService.GetIdentityForMembership(ctx, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get identity for membership", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
|
|
// Profile is the resolver for the profile field.
|
|
func (r *membershipResolver) Profile(ctx context.Context, obj *types.Membership) (*types.MembershipProfile, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.MembershipProfile{
|
|
ID: obj.Profile.ID,
|
|
}, nil
|
|
}
|
|
|
|
profile, err := r.iam.AccountService.GetProfileForMembership(ctx, obj.ID)
|
|
if err != nil {
|
|
var errProfileNotFound *iam.ErrProfileNotFound
|
|
if errors.As(err, &errProfileNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get profile for membership", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewMembershipProfile(profile), nil
|
|
}
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *membershipResolver) Organization(ctx context.Context, obj *types.Membership) (*types.Organization, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Organization{
|
|
ID: obj.Organization.ID,
|
|
}, nil
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.GetOrganizationForMembership(ctx, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get organization for membership", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
|
|
// Permissions is the resolver for the permissions field.
|
|
func (r *membershipResolver) Permissions(ctx context.Context, obj *types.Membership) ([]*types.Permission, error) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// LastSession is the resolver for the lastSession field.
|
|
func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) {
|
|
session := SessionFromContext(ctx)
|
|
if session == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID)
|
|
if err != nil {
|
|
var errSessionNotFound *iam.ErrSessionNotFound
|
|
if errors.As(err, &errSessionNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get active session for membership", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewSession(childSession), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *types.MembershipConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountMemberships(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count memberships", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
case *organizationResolver:
|
|
count, err := r.iam.OrganizationService.CountMemberships(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count memberships", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
// SignIn is the resolver for the signIn field.
|
|
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
|
// TODO: handle existing session to only open child session and chnage root session auth method to PASSWORD
|
|
|
|
user, session, err := r.iam.AuthService.OpenSessionWithPassword(ctx, input.Email, input.Password)
|
|
if err != nil {
|
|
var ErrInvalidCredentials *iam.ErrInvalidCredentials
|
|
if errors.As(err, &ErrInvalidCredentials) {
|
|
return nil, &gqlerror.Error{
|
|
Message: err.Error(),
|
|
Extensions: map[string]any{
|
|
"code": "INVALID_CREDENTIALS",
|
|
},
|
|
}
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot sign in", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
w := HTTPResponseWriterFromContext(ctx)
|
|
securecookie.Set(
|
|
w,
|
|
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
|
session.ID.String(),
|
|
)
|
|
|
|
return &types.SignInPayload{
|
|
Identity: types.NewIdentity(user),
|
|
Session: types.NewSession(session),
|
|
}, nil
|
|
}
|
|
|
|
// SignUp is the resolver for the signUp field.
|
|
func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput) (*types.SignUpPayload, error) {
|
|
identity, session, err := r.iam.AuthService.CreateIdentityWithPassword(
|
|
ctx,
|
|
&iam.CreateIdentityWithPasswordRequest{
|
|
Email: input.Email,
|
|
Password: input.Password,
|
|
FullName: input.FullName,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
|
|
if errors.As(err, &errIdentityAlreadyExists) {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
w := HTTPResponseWriterFromContext(ctx)
|
|
securecookie.Set(
|
|
w,
|
|
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
|
session.ID.String(),
|
|
)
|
|
|
|
return &types.SignUpPayload{
|
|
Identity: types.NewIdentity(identity),
|
|
}, nil
|
|
}
|
|
|
|
// SignOut is the resolver for the signOut field.
|
|
func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, error) {
|
|
session := SessionFromContext(ctx)
|
|
|
|
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
|
if err != nil {
|
|
var ErrSessionNotFound *iam.ErrSessionNotFound
|
|
if errors.As(err, &ErrSessionNotFound) {
|
|
return &types.SignOutPayload{}, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.SignOutPayload{Success: true}, nil
|
|
}
|
|
|
|
// SignUpFromInvitation is the resolver for the signUpFromInvitation field.
|
|
func (r *mutationResolver) SignUpFromInvitation(ctx context.Context, input types.SignUpFromInvitationInput) (*types.SignUpFromInvitationPayload, error) {
|
|
identity, session, err := r.iam.AuthService.CreateIdentityFromInvitation(
|
|
ctx,
|
|
&iam.CreateIdentityFromInvitationRequest{
|
|
InvitationToken: input.Token,
|
|
Password: input.Password,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidToken *iam.ErrInvalidToken
|
|
errInvitationNotFound *iam.ErrInvitationNotFound
|
|
errInvitationAlreadyAccepted *iam.ErrInvitationAlreadyAccepted
|
|
errInvitationExpired *iam.ErrInvitationExpired
|
|
errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
|
|
|
|
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
|
errors.As(err, &errInvitationNotFound) ||
|
|
errors.As(err, &errInvitationAlreadyAccepted) ||
|
|
errors.As(err, &errInvitationExpired)
|
|
)
|
|
|
|
if isInvalidErr {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityAlreadyExists) {
|
|
return nil, gqlutils.Conflict(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create identity from invitation", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
w := HTTPResponseWriterFromContext(ctx)
|
|
securecookie.Set(
|
|
w,
|
|
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
|
session.ID.String(),
|
|
)
|
|
|
|
return &types.SignUpFromInvitationPayload{
|
|
Identity: &types.Identity{
|
|
ID: identity.ID,
|
|
Email: identity.EmailAddress,
|
|
EmailVerified: identity.EmailAddressVerified,
|
|
CreatedAt: identity.CreatedAt,
|
|
UpdatedAt: identity.UpdatedAt,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// ForgotPassword is the resolver for the forgotPassword field.
|
|
func (r *mutationResolver) ForgotPassword(ctx context.Context, input types.ForgotPasswordInput) (*types.ForgotPasswordPayload, error) {
|
|
err := r.iam.AuthService.SendPasswordResetInstructionByEmail(
|
|
ctx,
|
|
input.Email,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot send password reset instruction by email", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.ForgotPasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ResetPassword is the resolver for the resetPassword field.
|
|
func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetPasswordInput) (*types.ResetPasswordPayload, error) {
|
|
err := r.iam.AuthService.ResetPassword(
|
|
ctx,
|
|
&iam.ResetPasswordRequest{
|
|
Token: input.Token,
|
|
Password: input.Password,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errInvalidToken *iam.ErrInvalidToken
|
|
if errors.As(err, &errInvalidToken) {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot reset password", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.ResetPasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// VerifyEmail is the resolver for the verifyEmail field.
|
|
func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) {
|
|
err := r.iam.AccountService.VerifyEmail(ctx, input.Token)
|
|
if err != nil {
|
|
var (
|
|
errInvalidToken *iam.ErrInvalidToken
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified
|
|
errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch
|
|
|
|
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
|
errors.As(err, &errEmailVerificationMismatch)
|
|
)
|
|
|
|
if isInvalidErr {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
if errors.As(err, &errEmailAlreadyVerified) {
|
|
return nil, gqlutils.Conflict(err)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot verify email", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.VerifyEmailPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ChangePassword is the resolver for the changePassword field.
|
|
func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.ChangePassword(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.ChangePasswordRequest{
|
|
CurrentPassword: input.CurrentPassword,
|
|
NewPassword: input.NewPassword,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidPassword *iam.ErrInvalidPassword
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
)
|
|
|
|
if errors.As(err, &errInvalidPassword) {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot change password", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.ChangePasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ChangeEmail is the resolver for the changeEmail field.
|
|
func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.ChangeEmail(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.ChangeEmailRequest{
|
|
NewEmail: input.NewEmail,
|
|
Password: input.Password,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidPassword *iam.ErrInvalidPassword
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
)
|
|
|
|
if errors.As(err, &errInvalidPassword) {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot change email", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.ChangeEmailPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// AssumeOrganizationSession is the resolver for the assumeOrganizationSession field.
|
|
func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input types.AssumeOrganizationSessionInput) (*types.AssumeOrganizationSessionPayload, error) {
|
|
rootSession := SessionFromContext(ctx)
|
|
|
|
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID)
|
|
if err != nil {
|
|
var (
|
|
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
errPasswordRequired *iam.ErrPasswordRequired
|
|
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
|
)
|
|
|
|
switch {
|
|
case errors.As(err, &errMembershipNotFound):
|
|
return nil, gqlutils.NotFound(err)
|
|
|
|
case errors.As(err, &errPasswordRequired):
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.PasswordRequired{
|
|
Reason: types.ReauthenticationReason(errPasswordRequired.Reason),
|
|
},
|
|
}, nil
|
|
|
|
case errors.As(err, &errSAMLAuthenticationRequired):
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.SAMLAuthenticationRequired{
|
|
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
|
RedirectURL: errSAMLAuthenticationRequired.RedirectURL,
|
|
},
|
|
}, nil
|
|
|
|
default:
|
|
r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
}
|
|
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.OrganizationSessionCreated{
|
|
Session: types.NewSession(childSession),
|
|
Membership: types.NewMembership(membership),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// RevokeSession is the resolver for the revokeSession field.
|
|
func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID)
|
|
if err != nil {
|
|
var ErrSessionExpired *iam.ErrSessionExpired
|
|
if errors.As(err, &ErrSessionExpired) {
|
|
return &types.RevokeSessionPayload{Success: true}, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot revoke session", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.RevokeSessionPayload{Success: true}, nil
|
|
}
|
|
|
|
// RevokeAllSessions is the resolver for the revokeAllSessions field.
|
|
func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) {
|
|
session := SessionFromContext(ctx)
|
|
|
|
revokedCount, err := r.iam.SessionService.RevokeAllSessions(ctx, session.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot revoke all sessions", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil
|
|
}
|
|
|
|
// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field.
|
|
func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey(
|
|
ctx,
|
|
identity.ID,
|
|
input.Name,
|
|
input.ExpiresAt,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create personal api key", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.CreatePersonalAPIKeyPayload{
|
|
PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt),
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// RevealPersonalAPIKeyToken is the resolver for the revealPersonalAPIKeyToken field.
|
|
func (r *mutationResolver) RevealPersonalAPIKeyToken(ctx context.Context, input types.RevealPersonalAPIKeyTokenInput) (*types.RevealPersonalAPIKeyTokenPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, input.TokenID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.RevealPersonalAPIKeyTokenPayload{Token: token}, nil
|
|
}
|
|
|
|
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
|
|
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.TokenID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.RevokePersonalAPIKeyPayload{Success: true}, nil
|
|
}
|
|
|
|
// CreateOrganization is the resolver for the createOrganization field.
|
|
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
var (
|
|
logoFile *iam.UploadedFile
|
|
horizontalLogoFile *iam.UploadedFile
|
|
)
|
|
|
|
if input.LogoFile != nil {
|
|
logoFile = &iam.UploadedFile{
|
|
Content: input.LogoFile.File,
|
|
Filename: input.LogoFile.Filename,
|
|
ContentType: input.LogoFile.ContentType,
|
|
Size: input.LogoFile.Size,
|
|
}
|
|
}
|
|
|
|
if input.HorizontalLogoFile != nil {
|
|
horizontalLogoFile = &iam.UploadedFile{
|
|
Content: input.HorizontalLogoFile.File,
|
|
Filename: input.HorizontalLogoFile.Filename,
|
|
ContentType: input.HorizontalLogoFile.ContentType,
|
|
Size: input.HorizontalLogoFile.Size,
|
|
}
|
|
}
|
|
organization, err := r.iam.OrganizationService.CreateOrganization(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.CreateOrganizationRequest{
|
|
Name: input.Name,
|
|
LogoFile: logoFile,
|
|
HorizontalLogoFile: horizontalLogoFile,
|
|
},
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.CreateOrganizationPayload{
|
|
Organization: types.NewOrganization(organization),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateOrganization is the resolver for the updateOrganization field.
|
|
func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) {
|
|
req := &iam.UpdateOrganizationRequest{
|
|
Name: input.Name,
|
|
Description: gqlutils.UnwrapOmittable(input.Description),
|
|
WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL),
|
|
Email: gqlutils.UnwrapOmittable(input.Email),
|
|
HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress),
|
|
}
|
|
|
|
if input.LogoFile != nil {
|
|
req.LogoFile = &iam.UploadedFile{
|
|
Filename: input.LogoFile.Filename,
|
|
ContentType: input.LogoFile.ContentType,
|
|
Size: input.LogoFile.Size,
|
|
Content: input.LogoFile.File,
|
|
}
|
|
}
|
|
|
|
if input.HorizontalLogoFile != nil {
|
|
req.HorizontalLogoFile = &iam.UploadedFile{
|
|
Filename: input.HorizontalLogoFile.Filename,
|
|
ContentType: input.HorizontalLogoFile.ContentType,
|
|
Size: input.HorizontalLogoFile.Size,
|
|
Content: input.HorizontalLogoFile.File,
|
|
}
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.UpdateOrganization(
|
|
ctx,
|
|
input.OrganizationID,
|
|
req,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.UpdateOrganizationPayload{
|
|
Organization: &types.Organization{
|
|
ID: organization.ID,
|
|
Name: organization.Name,
|
|
CreatedAt: organization.CreatedAt,
|
|
UpdatedAt: organization.UpdatedAt,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// DeleteOrganization is the resolver for the deleteOrganization field.
|
|
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
|
|
err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.DeleteOrganizationPayload{DeletedOrganizationID: input.OrganizationID}, nil
|
|
}
|
|
|
|
// DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field.
|
|
func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) {
|
|
panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo"))
|
|
}
|
|
|
|
// InviteMember is the resolver for the inviteMember field.
|
|
func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) {
|
|
invitation, err := r.iam.OrganizationService.InviteMember(
|
|
ctx,
|
|
input.OrganizationID,
|
|
input.Email,
|
|
input.FullName,
|
|
coredata.MembershipRoleViewer,
|
|
)
|
|
if err != nil {
|
|
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
var errMembershipAlreadyExists *iam.ErrMembershipAlreadyExists
|
|
|
|
if errors.As(err, &errOrganizationNotFound) {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
if errors.As(err, &errMembershipAlreadyExists) {
|
|
return nil, gqlutils.Conflict(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot add member to organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.InviteMemberPayload{
|
|
InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteInvitation is the resolver for the deleteInvitation field.
|
|
func (r *mutationResolver) DeleteInvitation(ctx context.Context, input types.DeleteInvitationInput) (*types.DeleteInvitationPayload, error) {
|
|
err := r.iam.OrganizationService.DeleteInvitation(ctx, input.OrganizationID, input.InvitationID)
|
|
if err != nil {
|
|
var errInvitationNotFound *iam.ErrInvitationNotFound
|
|
var errInvitationNotPending *iam.ErrInvitationNotPending
|
|
|
|
if errors.As(err, &errInvitationNotFound) {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
if errors.As(err, &errInvitationNotPending) {
|
|
return nil, gqlutils.Invalid(err, nil)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot delete invitation", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.DeleteInvitationPayload{DeletedInvitationID: input.InvitationID}, nil
|
|
}
|
|
|
|
// UpdateMembership is the resolver for the updateMembership field.
|
|
func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.UpdateMembershipInput) (*types.UpdateMembershipPayload, error) {
|
|
membership, err := r.iam.OrganizationService.UpdateMempership(ctx, input.OrganizationID, input.MembershipID, input.Role)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update membership", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.UpdateMembershipPayload{
|
|
Membership: types.NewMembership(membership),
|
|
}, nil
|
|
}
|
|
|
|
// RemoveMember is the resolver for the removeMember field.
|
|
func (r *mutationResolver) RemoveMember(ctx context.Context, input types.RemoveMemberInput) (*types.RemoveMemberPayload, error) {
|
|
err := r.iam.OrganizationService.RemoveMember(ctx, input.OrganizationID, input.MembershipID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot remove member from organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.RemoveMemberPayload{DeletedMembershipID: input.MembershipID}, nil
|
|
}
|
|
|
|
// AcceptInvitation is the resolver for the acceptInvitation field.
|
|
func (r *mutationResolver) AcceptInvitation(ctx context.Context, input types.AcceptInvitationInput) (*types.AcceptInvitationPayload, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
membership, err := r.iam.AccountService.AcceptInvitation(ctx, identity.ID, input.InvitationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot accept invitation", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.AcceptInvitationPayload{
|
|
MembershipEdge: types.NewMembershipEdge(membership, coredata.MembershipOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field.
|
|
func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) {
|
|
req := &iam.CreateSAMLConfigurationRequest{
|
|
EmailDomain: input.EmailDomain,
|
|
IdPEntityID: input.IdpEntityID,
|
|
IdPSsoURL: input.IdpSsoURL,
|
|
IdPCertificate: input.IdpCertificate,
|
|
AutoSignupEnabled: input.AutoSignupEnabled,
|
|
}
|
|
|
|
if input.AttributeMappings != nil {
|
|
req.AttributeEmail = input.AttributeMappings.Email
|
|
req.AttributeFirstname = input.AttributeMappings.FirstName
|
|
req.AttributeLastname = input.AttributeMappings.LastName
|
|
req.AttributeRole = input.AttributeMappings.Role
|
|
}
|
|
|
|
samlConfiguration, err := r.iam.OrganizationService.CreateSAMLConfiguration(
|
|
ctx,
|
|
input.OrganizationID,
|
|
req,
|
|
)
|
|
|
|
if err != nil {
|
|
var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists
|
|
if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) {
|
|
return nil, gqlutils.Conflict(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.CreateSAMLConfigurationPayload{
|
|
SamlConfigurationEdge: types.NewSAMLConfigurationEdge(
|
|
samlConfiguration,
|
|
coredata.SAMLConfigurationOrderFieldCreatedAt,
|
|
),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateSAMLConfiguration is the resolver for the updateSAMLConfiguration field.
|
|
func (r *mutationResolver) UpdateSAMLConfiguration(ctx context.Context, input types.UpdateSAMLConfigurationInput) (*types.UpdateSAMLConfigurationPayload, error) {
|
|
req := &iam.UpdateSAMLConfigurationRequest{
|
|
IdPEntityID: input.IdpEntityID,
|
|
IdPSsoURL: input.IdpSsoURL,
|
|
IdPCertificate: input.IdpCertificate,
|
|
AutoSignupEnabled: input.AutoSignupEnabled,
|
|
EnforcementPolicy: &input.EnforcementPolicy,
|
|
}
|
|
|
|
if input.AttributeMappings != nil {
|
|
req.AttributeEmail = input.AttributeMappings.Email
|
|
req.AttributeFirstname = input.AttributeMappings.FirstName
|
|
req.AttributeLastname = input.AttributeMappings.LastName
|
|
req.AttributeRole = input.AttributeMappings.Role
|
|
}
|
|
|
|
samlConfiguration, err := r.iam.OrganizationService.UpdateSAMLConfiguration(
|
|
ctx,
|
|
input.OrganizationID,
|
|
input.SamlConfigurationID,
|
|
req,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update saml configuration", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.UpdateSAMLConfigurationPayload{
|
|
SamlConfiguration: types.NewSAMLConfiguration(samlConfiguration),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteSAMLConfiguration is the resolver for the deleteSAMLConfiguration field.
|
|
func (r *mutationResolver) DeleteSAMLConfiguration(ctx context.Context, input types.DeleteSAMLConfigurationInput) (*types.DeleteSAMLConfigurationPayload, error) {
|
|
err := r.iam.OrganizationService.DeleteSAMLConfiguration(ctx, input.OrganizationID, input.SamlConfigurationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete saml configuration", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, nil
|
|
}
|
|
|
|
// LogoURL is the resolver for the logoUrl field.
|
|
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
|
presignedURL, err := r.iam.OrganizationService.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return presignedURL, nil
|
|
}
|
|
|
|
// HorizontalLogoURL is the resolver for the horizontalLogoUrl field.
|
|
func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
|
presignedURL, err := r.iam.OrganizationService.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot generate horizontal logo URL", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return presignedURL, nil
|
|
}
|
|
|
|
// Members is the resolver for the members field.
|
|
func (r *organizationResolver) Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.MembershipConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.MembershipOrderField]{
|
|
Field: coredata.MembershipOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy.Field = coredata.MembershipOrderField(orderBy.Field)
|
|
pageOrderBy.Direction = page.OrderDirection(orderBy.Direction)
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.OrganizationService.ListMembers(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list memberships", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewMembershipConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// Invitations is the resolver for the invitations field.
|
|
func (r *organizationResolver) Invitations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, status *coredata.InvitationStatus, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.InvitationConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
|
|
Field: coredata.InvitationOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
filters := coredata.NewInvitationFilter(nil)
|
|
if status != nil {
|
|
filters = coredata.NewInvitationFilter([]coredata.InvitationStatus{*status})
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.OrganizationService.ListInvitations(ctx, obj.ID, cursor, filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list invitations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewInvitationConnection(page, r, obj.ID, filters), nil
|
|
}
|
|
|
|
// SamlConfigurations is the resolver for the samlConfigurations field.
|
|
func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error) {
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.SAMLConfigurationConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.SAMLConfigurationOrderField]{
|
|
Field: coredata.SAMLConfigurationOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.OrganizationService.ListSAMLConfigurations(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// ViewerMembership is the resolver for the viewerMembership field.
|
|
func (r *organizationResolver) ViewerMembership(ctx context.Context, obj *types.Organization) (*types.Membership, error) {
|
|
identity := IdentityFromContext(ctx)
|
|
|
|
membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, identity.ID, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get membership for organization", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewMembership(membership), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountPersonalAPIKeys(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count personal api keys", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
// Node is the resolver for the node field.
|
|
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
var (
|
|
loadNode func(ctx context.Context, id gid.GID) (types.Node, error)
|
|
user = IdentityFromContext(ctx)
|
|
action string
|
|
)
|
|
|
|
switch id.EntityType() {
|
|
case coredata.OrganizationEntityType:
|
|
action = iam.ActionIAMOrganizationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
case coredata.IdentityEntityType:
|
|
action = iam.ActionIAMIdentityGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
identity, err := r.iam.AccountService.GetIdentity(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
case coredata.SessionEntityType:
|
|
action = iam.ActionIAMSessionGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
session, err := r.iam.GetSession(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewSession(session), nil
|
|
}
|
|
case coredata.MembershipEntityType:
|
|
action = iam.ActionIAMMembershipGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
membership, err := r.iam.GetMembership(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewMembership(membership), nil
|
|
}
|
|
case coredata.InvitationEntityType:
|
|
action = iam.ActionIAMInvitationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
invitation, err := r.iam.GetInvitation(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewInvitation(invitation), nil
|
|
}
|
|
case coredata.SAMLConfigurationEntityType:
|
|
action = iam.ActionIAMSAMLConfigurationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
samlConfiguration, err := r.iam.GetSAMLconfiguration(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewSAMLConfiguration(samlConfiguration), nil
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType())
|
|
}
|
|
|
|
err := r.iam.Authorizer.Authorize(
|
|
ctx,
|
|
iam.AuthorizeParams{
|
|
Principal: user.ID,
|
|
Resource: id,
|
|
Action: action,
|
|
ResourceAttributes: map[string]string{},
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errInsufficientPermissions *iam.ErrInsufficientPermissions
|
|
if errors.As(err, &errInsufficientPermissions) {
|
|
return nil, gqlutils.Forbidden(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
node, err := loadNode(ctx, id)
|
|
if err != nil {
|
|
var (
|
|
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
errSessionNotFound *iam.ErrSessionNotFound
|
|
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
errInvitationNotFound *iam.ErrInvitationNotFound
|
|
|
|
isNotFoundErr = errors.As(err, &errOrganizationNotFound) ||
|
|
errors.As(err, &errIdentityNotFound) ||
|
|
errors.As(err, &errSessionNotFound) ||
|
|
errors.As(err, &errMembershipNotFound) ||
|
|
errors.As(err, &errInvitationNotFound)
|
|
)
|
|
|
|
if isNotFoundErr {
|
|
return nil, gqlutils.NotFound(err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
// Viewer is the resolver for the viewer field.
|
|
func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) {
|
|
user := IdentityFromContext(ctx)
|
|
|
|
return &types.Identity{
|
|
ID: user.ID,
|
|
Email: user.EmailAddress,
|
|
EmailVerified: user.EmailAddressVerified,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// CheckSSOAvailability is the resolver for the checkSSOAvailability field.
|
|
func (r *queryResolver) CheckSSOAvailability(ctx context.Context, email string) (*types.SSOAvailability, error) {
|
|
panic(fmt.Errorf("not implemented: CheckSSOAvailability - checkSSOAvailability"))
|
|
}
|
|
|
|
// TestLoginURL is the resolver for the testLoginUrl field.
|
|
func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types.SAMLConfiguration) (string, error) {
|
|
return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := r.iam.OrganizationService.CountSAMLConfigurations(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count saml configurations", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
// Identity is the resolver for the identity field.
|
|
func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*types.Identity, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Identity{
|
|
ID: obj.Identity.ID,
|
|
}, nil
|
|
}
|
|
|
|
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get identity for session", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountSessions(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count sessions", log.Error(err))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.InternalServerError(ctx)
|
|
}
|
|
|
|
// Identity returns schema.IdentityResolver implementation.
|
|
func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} }
|
|
|
|
// Invitation returns schema.InvitationResolver implementation.
|
|
func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} }
|
|
|
|
// InvitationConnection returns schema.InvitationConnectionResolver implementation.
|
|
func (r *Resolver) InvitationConnection() schema.InvitationConnectionResolver {
|
|
return &invitationConnectionResolver{r}
|
|
}
|
|
|
|
// Membership returns schema.MembershipResolver implementation.
|
|
func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} }
|
|
|
|
// MembershipConnection returns schema.MembershipConnectionResolver implementation.
|
|
func (r *Resolver) MembershipConnection() schema.MembershipConnectionResolver {
|
|
return &membershipConnectionResolver{r}
|
|
}
|
|
|
|
// Mutation returns schema.MutationResolver implementation.
|
|
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
|
|
|
// Organization returns schema.OrganizationResolver implementation.
|
|
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
|
|
|
|
// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation.
|
|
func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver {
|
|
return &personalAPIKeyConnectionResolver{r}
|
|
}
|
|
|
|
// Query returns schema.QueryResolver implementation.
|
|
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
|
|
|
// SAMLConfiguration returns schema.SAMLConfigurationResolver implementation.
|
|
func (r *Resolver) SAMLConfiguration() schema.SAMLConfigurationResolver {
|
|
return &sAMLConfigurationResolver{r}
|
|
}
|
|
|
|
// SAMLConfigurationConnection returns schema.SAMLConfigurationConnectionResolver implementation.
|
|
func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnectionResolver {
|
|
return &sAMLConfigurationConnectionResolver{r}
|
|
}
|
|
|
|
// Session returns schema.SessionResolver implementation.
|
|
func (r *Resolver) Session() schema.SessionResolver { return &sessionResolver{r} }
|
|
|
|
// SessionConnection returns schema.SessionConnectionResolver implementation.
|
|
func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
|
|
return &sessionConnectionResolver{r}
|
|
}
|
|
|
|
type identityResolver struct{ *Resolver }
|
|
type invitationResolver struct{ *Resolver }
|
|
type invitationConnectionResolver struct{ *Resolver }
|
|
type membershipResolver struct{ *Resolver }
|
|
type membershipConnectionResolver struct{ *Resolver }
|
|
type mutationResolver struct{ *Resolver }
|
|
type organizationResolver struct{ *Resolver }
|
|
type personalAPIKeyConnectionResolver struct{ *Resolver }
|
|
type queryResolver struct{ *Resolver }
|
|
type sAMLConfigurationResolver struct{ *Resolver }
|
|
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
|
type sessionResolver struct{ *Resolver }
|
|
type sessionConnectionResolver struct{ *Resolver }
|
|
|
|
// !!! WARNING !!!
|
|
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
|
|
// one last chance to move it out of harms way if you want. There are two reasons this happens:
|
|
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
|
|
// it when you're done.
|
|
// - You have helper methods in this file. Move them out to keep these resolver files clean.
|
|
/*
|
|
func (r *mutationResolver) UpdatePersonalAPIKey(ctx context.Context, input types.UpdatePersonalAPIKeyInput) (*types.UpdatePersonalAPIKeyPayload, error) {
|
|
panic(fmt.Errorf("not implemented: UpdatePersonalAPIKey - updatePersonalAPIKey"))
|
|
}
|
|
*/
|