Split GraphQL schemas into per-entity files
Split each API's monolithic schema.graphql into per-coredata-model
files under graphql/ subdirectories. gqlgen's follow-schema layout
with {name}.resolvers.go template generates one resolver file per
schema file. Relay uses schema + schemaExtensions to load the split
files.
Connect API: 8 files (base, session, organization, profile,
personal_api_key, saml, scim, audit_log)
Trust API: 5 files (base, trust_center, auth, nda, mailing_list)
Console API: 25 files covering all domain entities
Types extended across files (Organization, Mutation, Viewer,
TrustCenter, Identity) are defined in base.graphql as required by
Relay's schemaExtensions.
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
100
pkg/server/api/connect/v1/audit_log.resolvers.go
Normal file
100
pkg/server/api/connect/v1/audit_log.resolvers.go
Normal file
@@ -0,0 +1,100 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) {
|
||||
return obj.Organization, nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) {
|
||||
filter := coredata.NewAuditLogEntryFilter()
|
||||
if obj.Filter != nil {
|
||||
filter = obj.Filter
|
||||
}
|
||||
|
||||
count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err))
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// AuditLogEntries is the resolver for the auditLogEntries field.
|
||||
func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{
|
||||
Field: coredata.AuditLogEntryOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
c := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
coredataFilter := coredata.NewAuditLogEntryFilter()
|
||||
if filter != nil {
|
||||
if filter.Action != nil {
|
||||
coredataFilter.WithAction(*filter.Action)
|
||||
}
|
||||
if filter.ActorID != nil {
|
||||
coredataFilter.WithActorID(*filter.ActorID)
|
||||
}
|
||||
if filter.ResourceType != nil {
|
||||
coredataFilter.WithResourceType(*filter.ResourceType)
|
||||
}
|
||||
if filter.ResourceID != nil {
|
||||
coredataFilter.WithResourceID(*filter.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, c, coredataFilter)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil
|
||||
}
|
||||
|
||||
// AuditLogEntry returns schema.AuditLogEntryResolver implementation.
|
||||
func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} }
|
||||
|
||||
// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation.
|
||||
func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver {
|
||||
return &auditLogEntryConnectionResolver{r}
|
||||
}
|
||||
|
||||
type auditLogEntryResolver struct{ *Resolver }
|
||||
type auditLogEntryConnectionResolver struct{ *Resolver }
|
||||
809
pkg/server/api/connect/v1/base.resolvers.go
Normal file
809
pkg/server/api/connect/v1/base.resolvers.go
Normal file
@@ -0,0 +1,809 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"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/mail"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
"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"
|
||||
)
|
||||
|
||||
// SsoLoginURL is the resolver for the ssoLoginURL field.
|
||||
func (r *identityResolver) SsoLoginURL(ctx context.Context, obj *types.Identity) (*string, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionIdentityGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, identity.EmailAddress)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
if count == 0 {
|
||||
return nil, graphql.ErrorOnPath(
|
||||
ctx,
|
||||
fmt.Errorf("no SAML configuration for email"),
|
||||
)
|
||||
}
|
||||
|
||||
return nil, graphql.ErrorOnPath(
|
||||
ctx,
|
||||
fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"),
|
||||
)
|
||||
}
|
||||
|
||||
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, identity.EmailAddress)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if len(samlConfigs) == 0 {
|
||||
r.logger.ErrorCtx(ctx, "cannot find SAML config")
|
||||
return nil, gqlutils.NotFoundf(ctx, "cannot find SAML config")
|
||||
}
|
||||
samlConfig := samlConfigs[0]
|
||||
|
||||
loginURL := r.SSOLoginURL(samlConfig.ID)
|
||||
return &loginURL, nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// SignIn is the resolver for the signIn field.
|
||||
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
||||
identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password)
|
||||
if err != nil {
|
||||
var errInvalidPassword *iam.ErrInvalidPassword
|
||||
if errors.As(err, &errInvalidPassword) {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
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 check credentials", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
session := authn.SessionFromContext(ctx)
|
||||
|
||||
switch {
|
||||
case session == nil:
|
||||
var err error
|
||||
session, err = r.iam.AuthService.OpenSessionWithPassword(
|
||||
ctx,
|
||||
identity.ID,
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
case session.IdentityID != identity.ID:
|
||||
if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
session, err = r.iam.AuthService.OpenSessionWithPassword(
|
||||
ctx,
|
||||
identity.ID,
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
||||
r.sessionCookie.Set(w, session)
|
||||
|
||||
if input.OrganizationID != nil {
|
||||
var err error
|
||||
_, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID)
|
||||
if err != nil {
|
||||
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
||||
var errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
var errUserInactive *iam.ErrUserInactive
|
||||
|
||||
if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) {
|
||||
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot assume organization", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return &types.SignInPayload{
|
||||
Identity: types.NewIdentity(identity),
|
||||
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(ctx, err)
|
||||
}
|
||||
|
||||
var errSignupDisabled *iam.ErrSignupDisabled
|
||||
if errors.As(err, &errSignupDisabled) {
|
||||
return nil, gqlutils.Forbidden(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
||||
r.sessionCookie.Set(w, session)
|
||||
|
||||
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 := authn.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.Internal(ctx)
|
||||
}
|
||||
|
||||
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
||||
r.sessionCookie.Clear(w)
|
||||
|
||||
return &types.SignOutPayload{Success: true}, nil
|
||||
}
|
||||
|
||||
// ActivateAccount is the resolver for the activateAccount field.
|
||||
func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) {
|
||||
session := authn.SessionFromContext(ctx)
|
||||
|
||||
if session != nil {
|
||||
// Sign out any other account before activating a new one
|
||||
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
||||
if err != nil {
|
||||
var ErrSessionNotFound *iam.ErrSessionNotFound
|
||||
if !errors.As(err, &ErrSessionNotFound) {
|
||||
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
||||
r.sessionCookie.Clear(w)
|
||||
}
|
||||
|
||||
identity, user, err := r.iam.AuthService.ActivateAccount(
|
||||
ctx,
|
||||
&iam.ActivateAccountRequest{
|
||||
InvitationToken: input.Token,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var (
|
||||
errInvalidToken *iam.ErrInvalidToken
|
||||
errInvitationNotFound *iam.ErrInvitationNotFound
|
||||
errInvitationExpired *iam.ErrInvitationExpired
|
||||
|
||||
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
||||
errors.As(err, &errInvitationNotFound) ||
|
||||
errors.As(err, &errInvitationExpired)
|
||||
)
|
||||
|
||||
if isInvalidErr {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrInvitationAlreadyAccepted](err); ok {
|
||||
return nil, gqlutils.AccountAlreadyActivated(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot activate account from invitation", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
var ssoLoginURL *string
|
||||
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, user.EmailAddress)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
for _, samlConfig := range samlConfigs {
|
||||
if samlConfig.OrganizationID != user.OrganizationID {
|
||||
continue
|
||||
}
|
||||
|
||||
ssoLoginURL = new(r.SSOLoginURL(samlConfig.ID))
|
||||
}
|
||||
|
||||
if ssoLoginURL != nil {
|
||||
return &types.ActivateAccountPayload{
|
||||
CreatePasswordToken: nil,
|
||||
SsoLoginURL: ssoLoginURL,
|
||||
Profile: types.NewProfile(user),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var createPasswordToken *string
|
||||
if identity.HashedPassword == nil {
|
||||
token, err := r.iam.AuthService.GetResetPasswordToken(ctx, identity.EmailAddress)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot generate password create token", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
createPasswordToken = &token
|
||||
}
|
||||
|
||||
return &types.ActivateAccountPayload{
|
||||
CreatePasswordToken: createPasswordToken,
|
||||
SsoLoginURL: nil,
|
||||
Profile: types.NewProfile(user),
|
||||
}, 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.Internal(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(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot reset password", log.Error(err))
|
||||
return nil, gqlutils.Internal(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(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errEmailAlreadyVerified) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errIdentityNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot verify email", log.Error(err))
|
||||
return nil, gqlutils.Internal(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 := authn.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(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errIdentityNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot change password", log.Error(err))
|
||||
return nil, gqlutils.Internal(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 := authn.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(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errIdentityNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot change email", log.Error(err))
|
||||
return nil, gqlutils.Internal(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 := authn.SessionFromContext(ctx)
|
||||
|
||||
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue)
|
||||
if err != nil {
|
||||
var (
|
||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired
|
||||
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
||||
)
|
||||
|
||||
switch {
|
||||
case errors.As(err, &errMembershipNotFound):
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
|
||||
case errors.As(err, &errPasswordAuthenticationRequired):
|
||||
return &types.AssumeOrganizationSessionPayload{
|
||||
Result: types.PasswordRequired{
|
||||
Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason),
|
||||
},
|
||||
}, nil
|
||||
|
||||
case errors.As(err, &errSAMLAuthenticationRequired):
|
||||
return &types.AssumeOrganizationSessionPayload{
|
||||
Result: types.SAMLAuthenticationRequired{
|
||||
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err))
|
||||
return nil, gqlutils.Internal(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) {
|
||||
if err := r.authorize(ctx, input.SessionID, iam.ActionSessionRevoke); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.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.Internal(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) {
|
||||
if err := r.authorize(ctx, authn.SessionFromContext(ctx).ID, iam.ActionSessionRevokeAll); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session := authn.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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil
|
||||
}
|
||||
|
||||
// LogoURL is the resolver for the logoUrl field.
|
||||
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return presignedURL, nil
|
||||
}
|
||||
|
||||
// HorizontalLogoURL is the resolver for the horizontalLogoUrl field.
|
||||
func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return presignedURL, nil
|
||||
}
|
||||
|
||||
// Viewer is the resolver for the viewer field.
|
||||
func (r *organizationResolver) Viewer(ctx context.Context, obj *types.Organization) (*types.Profile, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID)
|
||||
if err != nil {
|
||||
var errNotFound *iam.ErrProfileNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewProfile(profile), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// 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)
|
||||
action string
|
||||
)
|
||||
|
||||
switch id.EntityType() {
|
||||
case coredata.OrganizationEntityType:
|
||||
action = iam.ActionOrganizationGet
|
||||
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.ActionIdentityGet
|
||||
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.ActionSessionGet
|
||||
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.MembershipProfileEntityType:
|
||||
action = iam.ActionMembershipGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
profile, err := r.iam.OrganizationService.GetProfile(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewProfile(profile), nil
|
||||
}
|
||||
case coredata.MembershipEntityType:
|
||||
action = iam.ActionMembershipGet
|
||||
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.ActionInvitationGet
|
||||
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.ActionSAMLConfigurationGet
|
||||
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
|
||||
}
|
||||
case coredata.PersonalAPIKeyEntityType:
|
||||
action = iam.ActionPersonalAPIKeyGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
personalAPIKey, err := r.iam.GetPersonalAPIKey(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewPersonalAPIKey(personalAPIKey), nil
|
||||
}
|
||||
case coredata.SCIMConfigurationEntityType:
|
||||
action = iam.ActionSCIMConfigurationGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewSCIMConfiguration(scimConfiguration), nil
|
||||
}
|
||||
case coredata.SCIMEventEntityType:
|
||||
action = iam.ActionSCIMEventGet
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
scimEvent, err := r.iam.GetSCIMEvent(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewSCIMEvent(scimEvent), nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType())
|
||||
}
|
||||
|
||||
if err := r.authorize(ctx, id, action); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node, err := loadNode(ctx, id)
|
||||
if err != nil {
|
||||
var (
|
||||
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
errIdentityNotFound *iam.ErrIdentityNotFound
|
||||
errSessionNotFound *iam.ErrSessionNotFound
|
||||
errProfileNotFound *iam.ErrProfileNotFound
|
||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
errInvitationNotFound *iam.ErrInvitationNotFound
|
||||
|
||||
isNotFoundErr = errors.As(err, &errOrganizationNotFound) ||
|
||||
errors.As(err, &errIdentityNotFound) ||
|
||||
errors.As(err, &errSessionNotFound) ||
|
||||
errors.As(err, &errProfileNotFound) ||
|
||||
errors.As(err, &errMembershipNotFound) ||
|
||||
errors.As(err, &errInvitationNotFound)
|
||||
)
|
||||
|
||||
if isNotFoundErr {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Viewer is the resolver for the viewer field.
|
||||
func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) {
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
return &types.Identity{
|
||||
ID: identity.ID,
|
||||
Email: identity.EmailAddress,
|
||||
EmailVerified: identity.EmailAddressVerified,
|
||||
FullName: identity.FullName,
|
||||
CreatedAt: identity.CreatedAt,
|
||||
UpdatedAt: identity.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SsoLoginURL is the resolver for the ssoLoginURL field.
|
||||
func (r *queryResolver) SsoLoginURL(ctx context.Context, email mail.Addr) (*string, error) {
|
||||
count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, email)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
if count == 0 {
|
||||
return nil, graphql.ErrorOnPath(
|
||||
ctx,
|
||||
fmt.Errorf("no SAML configuration for email"),
|
||||
)
|
||||
}
|
||||
|
||||
return nil, graphql.ErrorOnPath(
|
||||
ctx,
|
||||
fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"),
|
||||
)
|
||||
}
|
||||
|
||||
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, email)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
samlConfig := samlConfigs[0]
|
||||
loginURL := r.SSOLoginURL(samlConfig.ID)
|
||||
|
||||
return &loginURL, nil
|
||||
}
|
||||
|
||||
// OidcProviders is the resolver for the oidcProviders field.
|
||||
func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) {
|
||||
providers := r.iam.OIDCService.EnabledProviders()
|
||||
result := make([]*types.OIDCProviderInfo, 0, len(providers))
|
||||
|
||||
for _, p := range providers {
|
||||
result = append(result, &types.OIDCProviderInfo{
|
||||
Name: strings.ToLower(p.String()),
|
||||
LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + strings.ToLower(p.String()) + "/login").MustString(),
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SignUpEnabled is the resolver for the signUpEnabled field.
|
||||
func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) {
|
||||
return r.iam.IsSignUpEnabled(), nil
|
||||
}
|
||||
|
||||
// Identity returns schema.IdentityResolver implementation.
|
||||
func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{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} }
|
||||
|
||||
// Query returns schema.QueryResolver implementation.
|
||||
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type identityResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type organizationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
@@ -1,5 +1,5 @@
|
||||
schema:
|
||||
- "schema.graphql"
|
||||
- "graphql/*.graphql"
|
||||
- "../../../gqlutils/directives/session/schema.graphql"
|
||||
|
||||
exec:
|
||||
@@ -14,7 +14,7 @@ resolver:
|
||||
layout: "follow-schema"
|
||||
dir: "."
|
||||
package: "connect_v1"
|
||||
filename_template: "v1_resolver.go"
|
||||
filename_template: "{name}.resolvers.go"
|
||||
|
||||
autobind: []
|
||||
call_argument_directives_with_null: true
|
||||
|
||||
83
pkg/server/api/connect/v1/graphql/audit_log.graphql
Normal file
83
pkg/server/api/connect/v1/graphql/audit_log.graphql
Normal file
@@ -0,0 +1,83 @@
|
||||
extend type Organization {
|
||||
auditLogEntries(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: AuditLogEntryOrder
|
||||
filter: AuditLogEntryFilter
|
||||
): AuditLogEntryConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
enum AuditLogActorType
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AuditLogActorType"
|
||||
) {
|
||||
USER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeUser"
|
||||
)
|
||||
API_KEY
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeAPIKey"
|
||||
)
|
||||
SYSTEM
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AuditLogActorTypeSystem"
|
||||
)
|
||||
}
|
||||
|
||||
enum AuditLogEntryOrderField
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderField"
|
||||
) {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.AuditLogEntryOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
input AuditLogEntryOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryOrderBy"
|
||||
) {
|
||||
field: AuditLogEntryOrderField!
|
||||
direction: OrderDirection!
|
||||
}
|
||||
|
||||
input AuditLogEntryFilter {
|
||||
action: String
|
||||
actorId: ID
|
||||
resourceType: String
|
||||
resourceId: ID
|
||||
}
|
||||
|
||||
type AuditLogEntry implements Node {
|
||||
id: ID!
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
actorId: ID!
|
||||
actorType: AuditLogActorType!
|
||||
action: String!
|
||||
resourceType: String!
|
||||
resourceId: ID!
|
||||
metadata: String
|
||||
createdAt: Datetime!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type AuditLogEntryConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.AuditLogEntryConnection"
|
||||
) {
|
||||
edges: [AuditLogEntryEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type AuditLogEntryEdge {
|
||||
cursor: CursorKey!
|
||||
node: AuditLogEntry!
|
||||
}
|
||||
121
pkg/server/api/connect/v1/graphql/base.graphql
Normal file
121
pkg/server/api/connect/v1/graphql/base.graphql
Normal file
@@ -0,0 +1,121 @@
|
||||
directive @goField(
|
||||
forceResolver: Boolean
|
||||
name: String
|
||||
omittable: Boolean
|
||||
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||
|
||||
directive @goModel(
|
||||
model: String
|
||||
models: [String!]
|
||||
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
|
||||
|
||||
directive @goEnum(value: String) on ENUM_VALUE
|
||||
|
||||
scalar CursorKey
|
||||
scalar Datetime
|
||||
scalar Upload
|
||||
scalar EmailAddr
|
||||
|
||||
enum OrderDirection
|
||||
@goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") {
|
||||
ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc")
|
||||
DESC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionDesc")
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
hasNextPage: Boolean!
|
||||
hasPreviousPage: Boolean!
|
||||
startCursor: CursorKey
|
||||
endCursor: CursorKey
|
||||
}
|
||||
|
||||
type OIDCProviderInfo {
|
||||
name: String!
|
||||
loginURL: String!
|
||||
}
|
||||
|
||||
enum ReauthenticationReason {
|
||||
SESSION_EXPIRED
|
||||
SENSITIVE_ACTION
|
||||
POLICY_REQUIREMENT
|
||||
}
|
||||
|
||||
type Query {
|
||||
node(id: ID!): Node @session(required: PRESENT)
|
||||
viewer: Identity @session(required: PRESENT)
|
||||
ssoLoginURL(email: EmailAddr!): String
|
||||
@goField(forceResolver: true)
|
||||
@session(required: OPTIONAL)
|
||||
oidcProviders: [OIDCProviderInfo!]!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: OPTIONAL)
|
||||
signUpEnabled: Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: OPTIONAL)
|
||||
}
|
||||
|
||||
type Identity implements Node {
|
||||
id: ID!
|
||||
email: EmailAddr!
|
||||
fullName: String!
|
||||
emailVerified: Boolean!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
ssoLoginURL: String
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
logoUrl: String @goField(forceResolver: true)
|
||||
horizontalLogoUrl: String @goField(forceResolver: true)
|
||||
email: String
|
||||
description: String
|
||||
websiteUrl: String
|
||||
headquarterAddress: String
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
viewer: Profile @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
signIn(input: SignInInput!): SignInPayload @session(required: OPTIONAL)
|
||||
signUp(input: SignUpInput!): SignUpPayload @session(required: NONE)
|
||||
signOut: SignOutPayload @session(required: PRESENT)
|
||||
activateAccount(
|
||||
input: ActivateAccountInput!
|
||||
): ActivateAccountPayload @session(required: OPTIONAL)
|
||||
forgotPassword(input: ForgotPasswordInput!): ForgotPasswordPayload
|
||||
@session(required: NONE)
|
||||
resetPassword(input: ResetPasswordInput!): ResetPasswordPayload
|
||||
@session(required: NONE)
|
||||
verifyEmail(input: VerifyEmailInput!): VerifyEmailPayload
|
||||
@session(required: OPTIONAL)
|
||||
changePassword(input: ChangePasswordInput!): ChangePasswordPayload
|
||||
@session(required: PRESENT)
|
||||
changeEmail(input: ChangeEmailInput!): ChangeEmailPayload
|
||||
@session(required: PRESENT)
|
||||
assumeOrganizationSession(
|
||||
input: AssumeOrganizationSessionInput!
|
||||
): AssumeOrganizationSessionPayload @session(required: PRESENT)
|
||||
|
||||
revokeSession(input: RevokeSessionInput!): RevokeSessionPayload!
|
||||
@session(required: PRESENT)
|
||||
revokeAllSessions: RevokeAllSessionsPayload @session(required: PRESENT)
|
||||
}
|
||||
56
pkg/server/api/connect/v1/graphql/organization.graphql
Normal file
56
pkg/server/api/connect/v1/graphql/organization.graphql
Normal file
@@ -0,0 +1,56 @@
|
||||
extend type Mutation {
|
||||
createOrganization(
|
||||
input: CreateOrganizationInput!
|
||||
): CreateOrganizationPayload @session(required: PRESENT)
|
||||
updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload @session(required: PRESENT)
|
||||
deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload @session(required: PRESENT)
|
||||
deleteOrganizationHorizontalLogo(
|
||||
input: DeleteOrganizationHorizontalLogoInput!
|
||||
): DeleteOrganizationHorizontalLogoPayload @session(required: PRESENT)
|
||||
}
|
||||
|
||||
input CreateOrganizationInput {
|
||||
name: String!
|
||||
logoFile: Upload
|
||||
horizontalLogoFile: Upload
|
||||
}
|
||||
|
||||
input UpdateOrganizationInput {
|
||||
organizationId: ID!
|
||||
name: String
|
||||
logoFile: Upload
|
||||
horizontalLogoFile: Upload
|
||||
description: String @goField(omittable: true)
|
||||
websiteUrl: String @goField(omittable: true)
|
||||
email: String @goField(omittable: true)
|
||||
headquarterAddress: String @goField(omittable: true)
|
||||
}
|
||||
|
||||
input DeleteOrganizationInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
input DeleteOrganizationHorizontalLogoInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
type CreateOrganizationPayload {
|
||||
organization: Organization
|
||||
profile: Profile!
|
||||
}
|
||||
|
||||
type UpdateOrganizationPayload {
|
||||
organization: Organization
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload {
|
||||
deletedOrganizationId: ID!
|
||||
}
|
||||
|
||||
type DeleteOrganizationHorizontalLogoPayload {
|
||||
organization: Organization!
|
||||
}
|
||||
63
pkg/server/api/connect/v1/graphql/personal_api_key.graphql
Normal file
63
pkg/server/api/connect/v1/graphql/personal_api_key.graphql
Normal file
@@ -0,0 +1,63 @@
|
||||
extend type Identity {
|
||||
personalAPIKeys(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): PersonalAPIKeyConnection @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type PersonalAPIKey implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
expiresAt: Datetime!
|
||||
lastUsedAt: Datetime
|
||||
createdAt: Datetime!
|
||||
|
||||
token: String @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type PersonalAPIKeyConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.PersonalAPIKeyConnection"
|
||||
) {
|
||||
edges: [PersonalAPIKeyEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type PersonalAPIKeyEdge {
|
||||
node: PersonalAPIKey!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createPersonalAPIKey(
|
||||
input: CreatePersonalAPIKeyInput!
|
||||
): CreatePersonalAPIKeyPayload @session(required: PRESENT)
|
||||
revokePersonalAPIKey(
|
||||
input: RevokePersonalAPIKeyInput!
|
||||
): RevokePersonalAPIKeyPayload @session(required: PRESENT)
|
||||
}
|
||||
|
||||
input CreatePersonalAPIKeyInput {
|
||||
name: String!
|
||||
expiresAt: Datetime!
|
||||
}
|
||||
|
||||
input RevokePersonalAPIKeyInput {
|
||||
personalAPIKeyId: ID!
|
||||
}
|
||||
|
||||
type CreatePersonalAPIKeyPayload {
|
||||
personalAPIKeyEdge: PersonalAPIKeyEdge!
|
||||
token: String!
|
||||
}
|
||||
|
||||
type RevokePersonalAPIKeyPayload {
|
||||
personalAPIKeyId: ID!
|
||||
}
|
||||
271
pkg/server/api/connect/v1/graphql/profile.graphql
Normal file
271
pkg/server/api/connect/v1/graphql/profile.graphql
Normal file
@@ -0,0 +1,271 @@
|
||||
extend type Identity {
|
||||
profiles(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ProfileOrder
|
||||
filter: ProfileFilter
|
||||
): ProfileConnection @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
extend type Organization {
|
||||
profiles(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ProfileOrder
|
||||
): ProfileConnection @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Profile implements Node {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
emailAddress: EmailAddr!
|
||||
source: String!
|
||||
state: ProfileState!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: String
|
||||
position: String
|
||||
contractStartDate: Datetime
|
||||
contractEndDate: Datetime
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
identity: Identity @goField(forceResolver: true)
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
membership: Membership @goField(forceResolver: true)
|
||||
pendingInvitations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: InvitationOrder
|
||||
): InvitationConnection @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
||||
INACTIVE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive")
|
||||
}
|
||||
|
||||
enum ProfileSource
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileSource") {
|
||||
MANUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceManual")
|
||||
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSAML")
|
||||
SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSCIM")
|
||||
}
|
||||
|
||||
enum MembershipRole
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
||||
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee")
|
||||
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
|
||||
AUDITOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor")
|
||||
}
|
||||
|
||||
type Membership implements Node {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
role: MembershipRole!
|
||||
|
||||
lastSession: Session @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type Invitation implements Node {
|
||||
id: ID!
|
||||
expiresAt: Datetime!
|
||||
acceptedAt: Datetime
|
||||
createdAt: Datetime!
|
||||
status: InvitationStatus!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum InvitationStatus
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") {
|
||||
PENDING
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending")
|
||||
ACCEPTED
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted")
|
||||
EXPIRED
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired")
|
||||
}
|
||||
|
||||
enum InvitationOrderField
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
input InvitationOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: InvitationOrderField!
|
||||
}
|
||||
|
||||
type InvitationConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection"
|
||||
) {
|
||||
edges: [InvitationEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type InvitationEdge {
|
||||
node: Invitation!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
enum ProfileOrderField
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderField") {
|
||||
FULL_NAME
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldFullName"
|
||||
)
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldCreatedAt"
|
||||
)
|
||||
KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldKind")
|
||||
ORGANIZATION_NAME
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldOrganizationName"
|
||||
)
|
||||
STATE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileOrderFieldState"
|
||||
)
|
||||
}
|
||||
|
||||
input ProfileFilter {
|
||||
excludeContractEnded: Boolean
|
||||
state: ProfileState
|
||||
}
|
||||
|
||||
input ProfileOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: ProfileOrderField!
|
||||
}
|
||||
|
||||
type ProfileConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileConnection"
|
||||
) {
|
||||
totalCount: Int @goField(forceResolver: true)
|
||||
edges: [ProfileEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type ProfileEdge {
|
||||
cursor: CursorKey!
|
||||
node: Profile!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createUser(input: CreateUserInput!): CreateUserPayload
|
||||
@session(required: PRESENT)
|
||||
inviteUser(input: InviteUserInput!): InviteUserPayload
|
||||
@session(required: PRESENT)
|
||||
deactivateUser(input: DeactivateUserInput!): DeactivateUserPayload
|
||||
updateUser(input: UpdateUserInput!): UpdateUserPayload!
|
||||
updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload!
|
||||
removeUser(input: RemoveUserInput!): RemoveUserPayload
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
input CreateUserInput {
|
||||
organizationId: ID!
|
||||
fullName: String!
|
||||
emailAddress: EmailAddr!
|
||||
role: MembershipRole!
|
||||
additionalEmailAddresses: [EmailAddr!]
|
||||
kind: String
|
||||
position: String
|
||||
contractStartDate: Datetime @goField(omittable: true)
|
||||
contractEndDate: Datetime @goField(omittable: true)
|
||||
}
|
||||
|
||||
input InviteUserInput {
|
||||
organizationId: ID!
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
input ActivateUserInput {
|
||||
organizationId: ID!
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
input DeactivateUserInput {
|
||||
organizationId: ID!
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
input UpdateUserInput {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
additionalEmailAddresses: [EmailAddr!]
|
||||
kind: String
|
||||
position: String
|
||||
contractStartDate: Datetime @goField(omittable: true)
|
||||
contractEndDate: Datetime @goField(omittable: true)
|
||||
}
|
||||
|
||||
input UpdateMembershipInput {
|
||||
organizationId: ID!
|
||||
membershipId: ID!
|
||||
role: MembershipRole!
|
||||
}
|
||||
|
||||
input RemoveUserInput {
|
||||
organizationId: ID!
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
type CreateUserPayload {
|
||||
profileEdge: ProfileEdge!
|
||||
}
|
||||
|
||||
type InviteUserPayload {
|
||||
invitationEdge: InvitationEdge!
|
||||
}
|
||||
|
||||
type DeactivateUserPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type UpdateUserPayload {
|
||||
profile: Profile!
|
||||
}
|
||||
|
||||
type UpdateMembershipPayload {
|
||||
membership: Membership!
|
||||
}
|
||||
|
||||
type RemoveUserPayload {
|
||||
deletedProfileId: ID!
|
||||
}
|
||||
119
pkg/server/api/connect/v1/graphql/saml.graphql
Normal file
119
pkg/server/api/connect/v1/graphql/saml.graphql
Normal file
@@ -0,0 +1,119 @@
|
||||
extend type Organization {
|
||||
samlConfigurations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): SAMLConfigurationConnection @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SAMLConfiguration implements Node {
|
||||
id: ID!
|
||||
emailDomain: String!
|
||||
enforcementPolicy: SAMLEnforcementPolicy!
|
||||
domainVerifiedAt: Datetime
|
||||
domainVerificationToken: String
|
||||
idpEntityId: String!
|
||||
idpSsoUrl: String!
|
||||
idpCertificate: String!
|
||||
autoSignupEnabled: Boolean!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
testLoginUrl: String! @goField(forceResolver: true)
|
||||
attributeMappings: SAMLAttributeMappings!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type SAMLAttributeMappings {
|
||||
email: String!
|
||||
firstName: String!
|
||||
lastName: String!
|
||||
role: String!
|
||||
}
|
||||
|
||||
enum SAMLEnforcementPolicy
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") {
|
||||
OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff")
|
||||
OPTIONAL
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional"
|
||||
)
|
||||
REQUIRED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyRequired"
|
||||
)
|
||||
}
|
||||
|
||||
type SAMLConfigurationConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SAMLConfigurationConnection"
|
||||
) {
|
||||
edges: [SAMLConfigurationEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SAMLConfigurationEdge {
|
||||
node: SAMLConfiguration!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createSAMLConfiguration(
|
||||
input: CreateSAMLConfigurationInput!
|
||||
): CreateSAMLConfigurationPayload @session(required: PRESENT)
|
||||
updateSAMLConfiguration(
|
||||
input: UpdateSAMLConfigurationInput!
|
||||
): UpdateSAMLConfigurationPayload @session(required: PRESENT)
|
||||
deleteSAMLConfiguration(
|
||||
input: DeleteSAMLConfigurationInput!
|
||||
): DeleteSAMLConfigurationPayload @session(required: PRESENT)
|
||||
}
|
||||
|
||||
input CreateSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
emailDomain: String!
|
||||
idpEntityId: String!
|
||||
idpSsoUrl: String!
|
||||
idpCertificate: String!
|
||||
autoSignupEnabled: Boolean!
|
||||
attributeMappings: SAMLAttributeMappingsInput
|
||||
}
|
||||
|
||||
input SAMLAttributeMappingsInput {
|
||||
email: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
role: String
|
||||
}
|
||||
|
||||
input UpdateSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
samlConfigurationId: ID!
|
||||
idpEntityId: String
|
||||
idpSsoUrl: String
|
||||
idpCertificate: String
|
||||
autoSignupEnabled: Boolean
|
||||
enforcementPolicy: SAMLEnforcementPolicy!
|
||||
attributeMappings: SAMLAttributeMappingsInput
|
||||
}
|
||||
|
||||
input DeleteSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
samlConfigurationId: ID!
|
||||
}
|
||||
|
||||
type CreateSAMLConfigurationPayload {
|
||||
samlConfigurationEdge: SAMLConfigurationEdge!
|
||||
}
|
||||
|
||||
type UpdateSAMLConfigurationPayload {
|
||||
samlConfiguration: SAMLConfiguration
|
||||
}
|
||||
|
||||
type DeleteSAMLConfigurationPayload {
|
||||
deletedSamlConfigurationId: ID!
|
||||
}
|
||||
182
pkg/server/api/connect/v1/graphql/scim.graphql
Normal file
182
pkg/server/api/connect/v1/graphql/scim.graphql
Normal file
@@ -0,0 +1,182 @@
|
||||
extend type Organization {
|
||||
scimConfiguration: SCIMConfiguration @goField(forceResolver: true)
|
||||
scimBridgeTypes: [SCIMBridgeTypeInfo!]! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SCIMConfiguration implements Node {
|
||||
id: ID!
|
||||
endpointUrl: String! @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
organization: Organization @goField(forceResolver: true)
|
||||
|
||||
bridge: SCIMBridge @goField(forceResolver: true)
|
||||
|
||||
events(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: SCIMEventOrder
|
||||
): SCIMEventConnection @goField(forceResolver: true)
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type SCIMBridge implements Node {
|
||||
id: ID!
|
||||
state: SCIMBridgeState!
|
||||
scimConfiguration: SCIMConfiguration @goField(forceResolver: true)
|
||||
connector: Connector @goField(forceResolver: true)
|
||||
type: SCIMBridgeType!
|
||||
excludedUserNames: [String!]!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type Connector implements Node {
|
||||
id: ID!
|
||||
provider: ConnectorProvider!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ConnectorProvider
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ConnectorProvider") {
|
||||
SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack")
|
||||
GOOGLE_WORKSPACE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace")
|
||||
BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex")
|
||||
TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally")
|
||||
CLOUDFLARE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare")
|
||||
}
|
||||
|
||||
enum SCIMBridgeType
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeType") {
|
||||
GOOGLE_WORKSPACE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeTypeGoogleWorkspace")
|
||||
}
|
||||
|
||||
type SCIMBridgeTypeInfo {
|
||||
type: SCIMBridgeType!
|
||||
oauth2Scopes: [String!]!
|
||||
}
|
||||
|
||||
enum SCIMBridgeState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMBridgeState") {
|
||||
PENDING @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStatePending")
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateActive")
|
||||
FAILED @goEnum(value: "go.probo.inc/probo/pkg/coredata.SCIMBridgeStateFailed")
|
||||
}
|
||||
|
||||
type SCIMEvent implements Node {
|
||||
id: ID!
|
||||
method: String!
|
||||
path: String!
|
||||
statusCode: Int!
|
||||
requestBody: String
|
||||
responseBody: String
|
||||
errorMessage: String
|
||||
userName: String!
|
||||
ipAddress: String!
|
||||
createdAt: Datetime!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum SCIMEventOrderField
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SCIMEventOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
input SCIMEventOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: SCIMEventOrderField!
|
||||
}
|
||||
|
||||
type SCIMEventConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SCIMEventConnection"
|
||||
) {
|
||||
edges: [SCIMEventEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SCIMEventEdge {
|
||||
node: SCIMEvent!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
createSCIMConfiguration(
|
||||
input: CreateSCIMConfigurationInput!
|
||||
): CreateSCIMConfigurationPayload @session(required: PRESENT)
|
||||
deleteSCIMConfiguration(
|
||||
input: DeleteSCIMConfigurationInput!
|
||||
): DeleteSCIMConfigurationPayload @session(required: PRESENT)
|
||||
regenerateSCIMToken(
|
||||
input: RegenerateSCIMTokenInput!
|
||||
): RegenerateSCIMTokenPayload @session(required: PRESENT)
|
||||
updateSCIMBridge(
|
||||
input: UpdateSCIMBridgeInput!
|
||||
): UpdateSCIMBridgePayload @session(required: PRESENT)
|
||||
}
|
||||
|
||||
input CreateSCIMConfigurationInput {
|
||||
organizationId: ID!
|
||||
connectorId: ID
|
||||
}
|
||||
|
||||
input DeleteSCIMConfigurationInput {
|
||||
organizationId: ID!
|
||||
scimConfigurationId: ID!
|
||||
}
|
||||
|
||||
input RegenerateSCIMTokenInput {
|
||||
organizationId: ID!
|
||||
scimConfigurationId: ID!
|
||||
}
|
||||
|
||||
input UpdateSCIMBridgeInput {
|
||||
organizationId: ID!
|
||||
scimBridgeId: ID!
|
||||
excludedUserNames: [String!]!
|
||||
}
|
||||
|
||||
type CreateSCIMConfigurationPayload {
|
||||
scimConfiguration: SCIMConfiguration!
|
||||
scimBridge: SCIMBridge
|
||||
token: String!
|
||||
}
|
||||
|
||||
type DeleteSCIMConfigurationPayload {
|
||||
deletedScimConfigurationId: ID!
|
||||
}
|
||||
|
||||
type RegenerateSCIMTokenPayload {
|
||||
scimConfiguration: SCIMConfiguration!
|
||||
token: String!
|
||||
}
|
||||
|
||||
type UpdateSCIMBridgePayload {
|
||||
scimBridge: SCIMBridge!
|
||||
}
|
||||
169
pkg/server/api/connect/v1/graphql/session.graphql
Normal file
169
pkg/server/api/connect/v1/graphql/session.graphql
Normal file
@@ -0,0 +1,169 @@
|
||||
enum SessionOrderField
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SessionOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldCreatedAt")
|
||||
EXPIRED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldExpiredAt")
|
||||
UPDATED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldUpdatedAt")
|
||||
}
|
||||
|
||||
input SessionOrder {
|
||||
direction: OrderDirection!
|
||||
field: SessionOrderField!
|
||||
}
|
||||
|
||||
extend type Identity {
|
||||
sessions(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: SessionOrder
|
||||
): SessionConnection @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Session implements Node {
|
||||
id: ID!
|
||||
identity: Identity @goField(forceResolver: true)
|
||||
ipAddress: String!
|
||||
userAgent: String!
|
||||
updatedAt: Datetime!
|
||||
createdAt: Datetime!
|
||||
expiresAt: Datetime!
|
||||
|
||||
permission(action: String!): Boolean!
|
||||
@goField(forceResolver: true)
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
type SessionConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SessionConnection"
|
||||
) {
|
||||
edges: [SessionEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SessionEdge {
|
||||
node: Session!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
input SignInInput {
|
||||
organizationId: ID
|
||||
email: EmailAddr!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input SignUpInput {
|
||||
email: EmailAddr!
|
||||
password: String!
|
||||
fullName: String!
|
||||
}
|
||||
|
||||
input ActivateAccountInput {
|
||||
token: String!
|
||||
}
|
||||
|
||||
input ForgotPasswordInput {
|
||||
email: EmailAddr!
|
||||
}
|
||||
|
||||
input ResetPasswordInput {
|
||||
token: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input VerifyEmailInput {
|
||||
token: String!
|
||||
}
|
||||
|
||||
input ChangePasswordInput {
|
||||
currentPassword: String!
|
||||
newPassword: String!
|
||||
}
|
||||
|
||||
input ChangeEmailInput {
|
||||
newEmail: EmailAddr!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input AssumeOrganizationSessionInput {
|
||||
organizationId: ID!
|
||||
continue: String!
|
||||
}
|
||||
|
||||
input RevokeSessionInput {
|
||||
sessionId: ID!
|
||||
}
|
||||
|
||||
type SignInPayload {
|
||||
identity: Identity
|
||||
session: Session
|
||||
}
|
||||
|
||||
type SignUpPayload {
|
||||
identity: Identity
|
||||
}
|
||||
|
||||
type SignOutPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ActivateAccountPayload {
|
||||
createPasswordToken: String
|
||||
ssoLoginUrl: String
|
||||
profile: Profile
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ResetPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type VerifyEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ChangePasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ChangeEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
union AssumeOrganizationSessionResult =
|
||||
| OrganizationSessionCreated
|
||||
| PasswordRequired
|
||||
| SAMLAuthenticationRequired
|
||||
|
||||
type OrganizationSessionCreated {
|
||||
session: Session!
|
||||
membership: Membership!
|
||||
}
|
||||
|
||||
type PasswordRequired {
|
||||
reason: ReauthenticationReason!
|
||||
}
|
||||
|
||||
type SAMLAuthenticationRequired {
|
||||
reason: ReauthenticationReason!
|
||||
}
|
||||
|
||||
type AssumeOrganizationSessionPayload {
|
||||
result: AssumeOrganizationSessionResult!
|
||||
}
|
||||
|
||||
type RevokeSessionPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type RevokeAllSessionsPayload {
|
||||
revokedCount: Int!
|
||||
}
|
||||
150
pkg/server/api/connect/v1/organization.resolvers.go
Normal file
150
pkg/server/api/connect/v1/organization.resolvers.go
Normal file
@@ -0,0 +1,150 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
)
|
||||
|
||||
// CreateOrganization is the resolver for the createOrganization field.
|
||||
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
// FIXME check email domain and related IDP config
|
||||
// if ok := r.authorize(ctx, identity.ID, iam.ActionOrganizationCreate); !ok {
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
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, profile, err := r.iam.OrganizationService.CreateOrganization(
|
||||
ctx,
|
||||
identity.ID,
|
||||
&iam.CreateOrganizationRequest{
|
||||
Name: input.Name,
|
||||
LogoFile: logoFile,
|
||||
HorizontalLogoFile: horizontalLogoFile,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create organization", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.CreateOrganizationPayload{
|
||||
Organization: types.NewOrganization(organization),
|
||||
Profile: types.NewProfile(profile),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateOrganization is the resolver for the updateOrganization field.
|
||||
func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateOrganizationPayload{
|
||||
Organization: &types.Organization{
|
||||
ID: organization.ID,
|
||||
Name: organization.Name,
|
||||
Description: organization.Description,
|
||||
WebsiteURL: organization.WebsiteURL,
|
||||
Email: organization.Email,
|
||||
HeadquarterAddress: organization.HeadquarterAddress,
|
||||
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) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot delete organization", log.Error(err))
|
||||
return nil, gqlutils.Internal(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"))
|
||||
}
|
||||
145
pkg/server/api/connect/v1/personal_api_key.resolvers.go
Normal file
145
pkg/server/api/connect/v1/personal_api_key.resolvers.go
Normal file
@@ -0,0 +1,145 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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 err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field.
|
||||
func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) {
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
if err := r.authorize(ctx, identity.ID, iam.ActionPersonalAPIKeyCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.CreatePersonalAPIKeyPayload{
|
||||
PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt),
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
|
||||
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
|
||||
if err := r.authorize(ctx, input.PersonalAPIKeyID, iam.ActionPersonalAPIKeyDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.PersonalAPIKeyID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RevokePersonalAPIKeyPayload{PersonalAPIKeyID: input.PersonalAPIKeyID}, nil
|
||||
}
|
||||
|
||||
// Token is the resolver for the token field.
|
||||
func (r *personalAPIKeyResolver) Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
|
||||
token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// 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:
|
||||
if err := r.authorize(ctx, obj.ParentID, iam.ActionPersonalAPIKeyList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &count, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// PersonalAPIKey returns schema.PersonalAPIKeyResolver implementation.
|
||||
func (r *Resolver) PersonalAPIKey() schema.PersonalAPIKeyResolver { return &personalAPIKeyResolver{r} }
|
||||
|
||||
// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation.
|
||||
func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver {
|
||||
return &personalAPIKeyConnectionResolver{r}
|
||||
}
|
||||
|
||||
type personalAPIKeyResolver struct{ *Resolver }
|
||||
type personalAPIKeyConnectionResolver struct{ *Resolver }
|
||||
441
pkg/server/api/connect/v1/profile.resolvers.go
Normal file
441
pkg/server/api/connect/v1/profile.resolvers.go
Normal file
@@ -0,0 +1,441 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Profiles is the resolver for the profiles field.
|
||||
func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filters := coredata.NewMembershipProfileFilter(nil).WithMembership()
|
||||
if filter != nil {
|
||||
filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership()
|
||||
if filter.State != nil {
|
||||
filters.WithState(*filter.State)
|
||||
}
|
||||
}
|
||||
|
||||
if gqlutils.OnlyTotalCountSelected(ctx) {
|
||||
return &types.ProfileConnection{
|
||||
Resolver: r,
|
||||
ParentID: obj.ID,
|
||||
Filters: filters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||
Field: coredata.MembershipProfileOrderFieldFullName,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filters)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewProfileConnection(page, r, obj.ID, filters), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *invitationResolver) Permission(ctx context.Context, obj *types.Invitation, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// LastSession is the resolver for the lastSession field.
|
||||
func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session := authn.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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSession(childSession), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *membershipResolver) Permission(ctx context.Context, obj *types.Membership, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// CreateUser is the resolver for the createUser field.
|
||||
func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUserInput) (*types.CreateUserPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionMembershipProfileCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile, err := r.iam.OrganizationService.CreateUser(
|
||||
ctx,
|
||||
&iam.CreateUserRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
EmailAddress: input.EmailAddress,
|
||||
Role: input.Role,
|
||||
FullName: input.FullName,
|
||||
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
|
||||
Kind: input.Kind,
|
||||
Position: input.Position,
|
||||
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
||||
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var errAlreadyExists *iam.ErrUserAlreadyExists
|
||||
if errors.As(err, &errAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create user", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.CreateUserPayload{
|
||||
ProfileEdge: types.NewProfileEdge(profile, coredata.MembershipProfileOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InviteUser is the resolver for the inviteUser field.
|
||||
func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error) {
|
||||
if err := r.authorize(ctx, input.ProfileID, iam.ActionInvitationCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invitation, err := r.iam.OrganizationService.InviteUser(
|
||||
ctx,
|
||||
&iam.CreateInvitationRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
ProfileID: input.ProfileID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
var errUserAlreadyExists *iam.ErrUserAlreadyExists
|
||||
|
||||
if errors.As(err, &errOrganizationNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errUserAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot invite user", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.InviteUserPayload{
|
||||
InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeactivateUser is the resolver for the deactivateUser field.
|
||||
func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.DeactivateUserInput) (*types.DeactivateUserPayload, error) {
|
||||
if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := r.iam.OrganizationService.UpdateUserState(
|
||||
ctx,
|
||||
input.ProfileID,
|
||||
coredata.ProfileStateInactive,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DeactivateUserPayload{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateUser is the resolver for the updateUser field.
|
||||
func (r *mutationResolver) UpdateUser(ctx context.Context, input types.UpdateUserInput) (*types.UpdateUserPayload, error) {
|
||||
if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile, err := r.iam.OrganizationService.UpdateUser(
|
||||
ctx,
|
||||
&iam.UpdateUserRequest{
|
||||
ID: input.ID,
|
||||
FullName: input.FullName,
|
||||
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
|
||||
Kind: input.Kind,
|
||||
Position: input.Position,
|
||||
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
||||
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateUserPayload{
|
||||
Profile: types.NewProfile(profile),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateMembership is the resolver for the updateMembership field.
|
||||
func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.UpdateMembershipInput) (*types.UpdateMembershipPayload, error) {
|
||||
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if input.Role == coredata.MembershipRoleOwner {
|
||||
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipRoleSetOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateMembershipPayload{
|
||||
Membership: types.NewMembership(membership),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RemoveUser is the resolver for the removeUser field.
|
||||
func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) {
|
||||
if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID)
|
||||
if err != nil {
|
||||
var errManagedBySCIM *iam.ErrUserManagedBySCIM
|
||||
var errLastActiveOwner *iam.ErrLastActiveOwner
|
||||
|
||||
if errors.As(err, &errManagedBySCIM) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errLastActiveOwner) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot remove user from organization", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RemoveUserPayload{DeletedProfileID: input.ProfileID}, nil
|
||||
}
|
||||
|
||||
// Profiles is the resolver for the profiles field.
|
||||
func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := coredata.NewMembershipProfileFilter(nil).WithMembership()
|
||||
|
||||
if gqlutils.OnlyTotalCountSelected(ctx) {
|
||||
return &types.ProfileConnection{
|
||||
Resolver: r,
|
||||
ParentID: obj.ID,
|
||||
Filters: filter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||
Field: coredata.MembershipProfileOrderFieldFullName,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filter)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewProfileConnection(page, r, obj.ID, filter), nil
|
||||
}
|
||||
|
||||
// Identity is the resolver for the identity field.
|
||||
func (r *profileResolver) Identity(ctx context.Context, obj *types.Profile) (*types.Identity, error) {
|
||||
if err := r.authorize(
|
||||
ctx,
|
||||
obj.ID,
|
||||
iam.ActionMembershipProfileGet,
|
||||
authz.WithSkipAssumptionCheck(),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
||||
if err != nil {
|
||||
var errNotFound *iam.ErrIdentityNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get identity", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewIdentity(identity), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *profileResolver) Organization(ctx context.Context, obj *types.Profile) (*types.Organization, error) {
|
||||
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
||||
if err != nil {
|
||||
var errNotFound *iam.ErrOrganizationNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Membership is the resolver for the membership field.
|
||||
func (r *profileResolver) Membership(ctx context.Context, obj *types.Profile) (*types.Membership, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID)
|
||||
if err != nil {
|
||||
var errNotFound *iam.ErrMembershipNotFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get membership", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewMembership(membership), nil
|
||||
}
|
||||
|
||||
// PendingInvitations is the resolver for the pendingInvitations field.
|
||||
func (r *profileResolver) PendingInvitations(ctx context.Context, obj *types.Profile, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionInvitationList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewInvitationConnection(page, r, obj.ID, nil), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (*int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *identityResolver:
|
||||
count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, obj.Filters)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
return &count, nil
|
||||
case *organizationResolver:
|
||||
count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
return &count, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// Invitation returns schema.InvitationResolver implementation.
|
||||
func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} }
|
||||
|
||||
// Membership returns schema.MembershipResolver implementation.
|
||||
func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} }
|
||||
|
||||
// Profile returns schema.ProfileResolver implementation.
|
||||
func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} }
|
||||
|
||||
// ProfileConnection returns schema.ProfileConnectionResolver implementation.
|
||||
func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver {
|
||||
return &profileConnectionResolver{r}
|
||||
}
|
||||
|
||||
type invitationResolver struct{ *Resolver }
|
||||
type membershipResolver struct{ *Resolver }
|
||||
type profileResolver struct{ *Resolver }
|
||||
type profileConnectionResolver struct{ *Resolver }
|
||||
185
pkg/server/api/connect/v1/saml.resolvers.go
Normal file
185
pkg/server/api/connect/v1/saml.resolvers.go
Normal file
@@ -0,0 +1,185 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"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"
|
||||
)
|
||||
|
||||
// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field.
|
||||
func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err))
|
||||
return nil, gqlutils.Internal(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) {
|
||||
if err := r.authorize(ctx, input.SamlConfigurationID, iam.ActionSAMLConfigurationUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(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) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, 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 err := r.authorize(ctx, obj.ID, iam.ActionSAMLConfigurationList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sAMLConfigurationResolver) Permission(ctx context.Context, obj *types.SAMLConfiguration, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// 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.Internal(ctx)
|
||||
}
|
||||
return &count, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
type sAMLConfigurationResolver struct{ *Resolver }
|
||||
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
||||
File diff suppressed because it is too large
Load Diff
331
pkg/server/api/connect/v1/scim.resolvers.go
Normal file
331
pkg/server/api/connect/v1/scim.resolvers.go
Normal file
@@ -0,0 +1,331 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/iam/scim/bridge/provider/googleworkspace"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *connectorResolver) Permission(ctx context.Context, obj *types.Connector, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// CreateSCIMConfiguration is the resolver for the createSCIMConfiguration field.
|
||||
func (r *mutationResolver) CreateSCIMConfiguration(ctx context.Context, input types.CreateSCIMConfigurationInput) (*types.CreateSCIMConfigurationPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, token, err := r.iam.OrganizationService.CreateSCIMConfiguration(ctx, input.OrganizationID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot create scim configuration", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
var bridge *types.SCIMBridge
|
||||
|
||||
if input.ConnectorID != nil {
|
||||
scimBridge, err := r.iam.OrganizationService.CreateSCIMBridge(ctx, input.OrganizationID, config.ID, *input.ConnectorID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot create scim bridge", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
bridge = types.NewSCIMBridge(scimBridge)
|
||||
}
|
||||
|
||||
payload := &types.CreateSCIMConfigurationPayload{
|
||||
ScimConfiguration: types.NewSCIMConfiguration(config),
|
||||
ScimBridge: bridge,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// DeleteSCIMConfiguration is the resolver for the deleteSCIMConfiguration field.
|
||||
func (r *mutationResolver) DeleteSCIMConfiguration(ctx context.Context, input types.DeleteSCIMConfigurationInput) (*types.DeleteSCIMConfigurationPayload, error) {
|
||||
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationDelete); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := r.iam.OrganizationService.DeleteSCIMConfiguration(ctx, input.OrganizationID, input.ScimConfigurationID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot delete scim configuration", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DeleteSCIMConfigurationPayload{DeletedScimConfigurationID: input.ScimConfigurationID}, nil
|
||||
}
|
||||
|
||||
// RegenerateSCIMToken is the resolver for the regenerateSCIMToken field.
|
||||
func (r *mutationResolver) RegenerateSCIMToken(ctx context.Context, input types.RegenerateSCIMTokenInput) (*types.RegenerateSCIMTokenPayload, error) {
|
||||
if err := r.authorize(ctx, input.ScimConfigurationID, iam.ActionSCIMConfigurationUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, token, err := r.iam.OrganizationService.RegenerateSCIMToken(ctx, input.OrganizationID, input.ScimConfigurationID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot regenerate scim token", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.RegenerateSCIMTokenPayload{
|
||||
ScimConfiguration: types.NewSCIMConfiguration(config),
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateSCIMBridge is the resolver for the updateSCIMBridge field.
|
||||
func (r *mutationResolver) UpdateSCIMBridge(ctx context.Context, input types.UpdateSCIMBridgeInput) (*types.UpdateSCIMBridgePayload, error) {
|
||||
if err := r.authorize(ctx, input.ScimBridgeID, iam.ActionSCIMBridgeUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bridge, err := r.iam.OrganizationService.UpdateSCIMBridge(ctx, input.OrganizationID, input.ScimBridgeID, input.ExcludedUserNames)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot update scim bridge excluded user names", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.UpdateSCIMBridgePayload{
|
||||
ScimBridge: types.NewSCIMBridge(bridge),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ScimConfiguration is the resolver for the scimConfiguration field.
|
||||
func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types.Organization) (*types.SCIMConfiguration, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID)
|
||||
if err != nil {
|
||||
var notFound *iam.ErrNoSCIMConfigurationFound
|
||||
if errors.As(err, ¬Found) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get scim configuration", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSCIMConfiguration(config), nil
|
||||
}
|
||||
|
||||
// ScimBridgeTypes is the resolver for the scimBridgeTypes field.
|
||||
func (r *organizationResolver) ScimBridgeTypes(ctx context.Context, obj *types.Organization) ([]*types.SCIMBridgeTypeInfo, error) {
|
||||
return []*types.SCIMBridgeTypeInfo{
|
||||
{
|
||||
Type: coredata.SCIMBridgeTypeGoogleWorkspace,
|
||||
Oauth2Scopes: googleworkspace.OAuth2Scopes,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ScimConfiguration is the resolver for the scimConfiguration field.
|
||||
func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.SCIMBridge) (*types.SCIMConfiguration, error) {
|
||||
if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gqlutils.OnlyIDSelected(ctx) {
|
||||
return &types.SCIMConfiguration{
|
||||
ID: obj.ScimConfiguration.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID)
|
||||
if err != nil {
|
||||
var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound
|
||||
if errors.As(err, &errNoSCIMConfigurationFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewSCIMConfiguration(scimConfiguration), nil
|
||||
}
|
||||
|
||||
// Connector is the resolver for the connector field.
|
||||
func (r *sCIMBridgeResolver) Connector(ctx context.Context, obj *types.SCIMBridge) (*types.Connector, error) {
|
||||
if obj.Connector == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Authorize based on the SCIM configuration (connector accessed via bridge is a sub-resource)
|
||||
if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gqlutils.OnlyIDSelected(ctx) {
|
||||
return &types.Connector{
|
||||
ID: obj.Connector.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Use metadata-only loading since we don't need the encrypted connection data
|
||||
connector, err := r.iam.OrganizationService.GetConnectorMetadataByID(ctx, obj.Connector.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot get connector", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewConnector(connector), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sCIMBridgeResolver) Permission(ctx context.Context, obj *types.SCIMBridge, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// EndpointURL is the resolver for the endpointUrl field.
|
||||
func (r *sCIMConfigurationResolver) EndpointURL(ctx context.Context, obj *types.SCIMConfiguration) (string, error) {
|
||||
return r.baseURL.WithPath("/api/connect/v1/scim/2.0").MustString(), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *sCIMConfigurationResolver) Organization(ctx context.Context, obj *types.SCIMConfiguration) (*types.Organization, error) {
|
||||
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gqlutils.OnlyIDSelected(ctx) {
|
||||
return &types.Organization{
|
||||
ID: obj.Organization.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
||||
if err != nil {
|
||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
if errors.As(err, &errOrganizationNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get organization for scim configuration", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Bridge is the resolver for the bridge field.
|
||||
func (r *sCIMConfigurationResolver) Bridge(ctx context.Context, obj *types.SCIMConfiguration) (*types.SCIMBridge, error) {
|
||||
if obj.Bridge == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID)
|
||||
if err != nil {
|
||||
var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound
|
||||
if errors.As(err, &errSCIMBridgeNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get scim bridge", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSCIMBridge(bridge), nil
|
||||
}
|
||||
|
||||
// Events is the resolver for the events field.
|
||||
func (r *sCIMConfigurationResolver) Events(ctx context.Context, obj *types.SCIMConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SCIMEventOrderBy) (*types.SCIMEventConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMEventList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.SCIMEventOrderField]{
|
||||
Field: coredata.SCIMEventOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy.Field = coredata.SCIMEventOrderField(orderBy.Field)
|
||||
pageOrderBy.Direction = page.OrderDirection(orderBy.Direction)
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
events, err := r.iam.OrganizationService.ListSCIMEventsByConfigID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list scim events", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSCIMEventConnection(events, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sCIMConfigurationResolver) Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *sCIMEventConnectionResolver) TotalCount(ctx context.Context, obj *types.SCIMEventConnection) (*int, error) {
|
||||
if err := r.authorize(ctx, obj.ParentID, iam.ActionSCIMEventList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch obj.Resolver.(type) {
|
||||
case *sCIMConfigurationResolver:
|
||||
count, err := r.iam.OrganizationService.CountSCIMEvents(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count scim events", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
return &count, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// Connector returns schema.ConnectorResolver implementation.
|
||||
func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} }
|
||||
|
||||
// SCIMBridge returns schema.SCIMBridgeResolver implementation.
|
||||
func (r *Resolver) SCIMBridge() schema.SCIMBridgeResolver { return &sCIMBridgeResolver{r} }
|
||||
|
||||
// SCIMConfiguration returns schema.SCIMConfigurationResolver implementation.
|
||||
func (r *Resolver) SCIMConfiguration() schema.SCIMConfigurationResolver {
|
||||
return &sCIMConfigurationResolver{r}
|
||||
}
|
||||
|
||||
// SCIMEvent returns schema.SCIMEventResolver implementation.
|
||||
func (r *Resolver) SCIMEvent() schema.SCIMEventResolver { return &sCIMEventResolver{r} }
|
||||
|
||||
// SCIMEventConnection returns schema.SCIMEventConnectionResolver implementation.
|
||||
func (r *Resolver) SCIMEventConnection() schema.SCIMEventConnectionResolver {
|
||||
return &sCIMEventConnectionResolver{r}
|
||||
}
|
||||
|
||||
type connectorResolver struct{ *Resolver }
|
||||
type sCIMBridgeResolver struct{ *Resolver }
|
||||
type sCIMConfigurationResolver struct{ *Resolver }
|
||||
type sCIMEventResolver struct{ *Resolver }
|
||||
type sCIMEventConnectionResolver struct{ *Resolver }
|
||||
104
pkg/server/api/connect/v1/session.resolvers.go
Normal file
104
pkg/server/api/connect/v1/session.resolvers.go
Normal file
@@ -0,0 +1,104 @@
|
||||
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.87
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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 err := r.authorize(ctx, obj.ID, iam.ActionSessionList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewSessionConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// 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.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewIdentity(identity), nil
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sessionResolver) Permission(ctx context.Context, obj *types.Session, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// 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.Internal(ctx)
|
||||
}
|
||||
|
||||
return &count, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// 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 sessionResolver struct{ *Resolver }
|
||||
type sessionConnectionResolver struct{ *Resolver }
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user