diff --git a/pkg/server/api/connect/v1/base.resolvers.go b/pkg/server/api/connect/v1/base.resolvers.go index a9629e840..834914317 100644 --- a/pkg/server/api/connect/v1/base.resolvers.go +++ b/pkg/server/api/connect/v1/base.resolvers.go @@ -12,628 +12,17 @@ import ( "strings" "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/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 -} - -// 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 -} - -// 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 -} - -// 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 -} - // Node is the resolver for the node field. func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { var ( @@ -843,15 +232,11 @@ 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} } // Query returns schema.QueryResolver implementation. func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } -type identityResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/graphql/base.graphql b/pkg/server/api/connect/v1/graphql/base.graphql index 1f90ecab3..4f27b4294 100644 --- a/pkg/server/api/connect/v1/graphql/base.graphql +++ b/pkg/server/api/connect/v1/graphql/base.graphql @@ -16,34 +16,10 @@ 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) @@ -58,69 +34,4 @@ type Query { @session(required: OPTIONAL) } -type Identity implements Node { - id: ID! - email: EmailAddr! - fullName: String! - emailVerified: Boolean! - createdAt: Datetime! - updatedAt: Datetime! - - profiles( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ProfileOrder - filter: ProfileFilter - ): ProfileConnection @goField(forceResolver: true) - - sessions( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: SessionOrder - ): SessionConnection @goField(forceResolver: true) - - personalAPIKeys( - first: Int - after: CursorKey - last: Int - before: CursorKey - ): PersonalAPIKeyConnection @goField(forceResolver: true) - - ssoLoginURL: String - @goField(forceResolver: true) - @session(required: PRESENT) - - 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) -} +type Mutation diff --git a/pkg/server/api/connect/v1/graphql/identity.graphql b/pkg/server/api/connect/v1/graphql/identity.graphql new file mode 100644 index 000000000..e8a0bcf94 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/identity.graphql @@ -0,0 +1,40 @@ +type Identity implements Node { + id: ID! + email: EmailAddr! + fullName: String! + emailVerified: Boolean! + createdAt: Datetime! + updatedAt: Datetime! + + profiles( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + filter: ProfileFilter + ): ProfileConnection @goField(forceResolver: true) + + sessions( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: SessionOrder + ): SessionConnection @goField(forceResolver: true) + + personalAPIKeys( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): PersonalAPIKeyConnection @goField(forceResolver: true) + + ssoLoginURL: String + @goField(forceResolver: true) + @session(required: PRESENT) + + permission(action: String!): Boolean! + @goField(forceResolver: true) + @session(required: PRESENT) +} diff --git a/pkg/server/api/connect/v1/graphql/oidc.graphql b/pkg/server/api/connect/v1/graphql/oidc.graphql new file mode 100644 index 000000000..016b85698 --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/oidc.graphql @@ -0,0 +1,4 @@ +type OIDCProviderInfo { + name: String! + loginURL: String! +} diff --git a/pkg/server/api/connect/v1/graphql/pagination.graphql b/pkg/server/api/connect/v1/graphql/pagination.graphql new file mode 100644 index 000000000..dc748d8ad --- /dev/null +++ b/pkg/server/api/connect/v1/graphql/pagination.graphql @@ -0,0 +1,12 @@ +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: CursorKey + endCursor: CursorKey +} + +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") +} diff --git a/pkg/server/api/connect/v1/graphql/session.graphql b/pkg/server/api/connect/v1/graphql/session.graphql index 54b4c3020..5b829a5b3 100644 --- a/pkg/server/api/connect/v1/graphql/session.graphql +++ b/pkg/server/api/connect/v1/graphql/session.graphql @@ -1,3 +1,35 @@ +enum ReauthenticationReason { + SESSION_EXPIRED + SENSITIVE_ACTION + POLICY_REQUIREMENT +} + +extend 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) +} + enum SessionOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.SessionOrderField") { CREATED_AT diff --git a/pkg/server/api/connect/v1/identity.resolvers.go b/pkg/server/api/connect/v1/identity.resolvers.go new file mode 100644 index 000000000..febdfcca0 --- /dev/null +++ b/pkg/server/api/connect/v1/identity.resolvers.go @@ -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" + "fmt" + + "github.com/99designs/gqlgen/graphql" + "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 +} + +// 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 +} + +// 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 +} + +// 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) +} + +// Identity returns schema.IdentityResolver implementation. +func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} } + +type identityResolver struct{ *Resolver } diff --git a/pkg/server/api/connect/v1/session.resolvers.go b/pkg/server/api/connect/v1/session.resolvers.go index aa7a65707..7f5b05a79 100644 --- a/pkg/server/api/connect/v1/session.resolvers.go +++ b/pkg/server/api/connect/v1/session.resolvers.go @@ -7,13 +7,467 @@ package connect_v1 import ( "context" + "errors" + "github.com/vektah/gqlparser/v2/gqlerror" "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/iam" + "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" ) +// 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 +} + // 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) { diff --git a/pkg/server/api/console/v1/base.resolvers.go b/pkg/server/api/console/v1/base.resolvers.go index 2bd0e9874..d32d4efef 100644 --- a/pkg/server/api/console/v1/base.resolvers.go +++ b/pkg/server/api/console/v1/base.resolvers.go @@ -9,13 +9,11 @@ import ( "context" "errors" "fmt" - "time" "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/console/v1/schema" @@ -24,23 +22,6 @@ import ( "go.probo.inc/probo/pkg/validator" ) -// DownloadURL is the resolver for the downloadUrl field. -func (r *fileResolver) DownloadURL(ctx context.Context, obj *types.File) (string, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { - return "", err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - downloadUrl, err := prb.Files.GenerateFileTempURL(ctx, obj.ID, 60*time.Second) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) - return "", gqlutils.Internal(ctx) - } - - return downloadUrl, nil -} - // UpdateOrganizationContext is the resolver for the updateOrganizationContext field. func (r *mutationResolver) UpdateOrganizationContext(ctx context.Context, input types.UpdateOrganizationContextInput) (*types.UpdateOrganizationContextPayload, error) { if err := r.authorize(ctx, input.OrganizationID, probo.ActionOrganizationContextUpdate); err != nil { @@ -431,177 +412,11 @@ func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) { return &types.Viewer{ID: viewerID}, nil } -// SignableDocuments is the resolver for the signableDocuments field. -func (r *viewerResolver) SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { - if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, organizationID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) - - documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization signable documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) - for i, doc := range documentsPage.Data { - employeeDocuments[i] = &types.EmployeeDocument{ - ID: doc.ID, - Title: doc.Title, - DocumentType: doc.DocumentType, - CreatedAt: doc.CreatedAt, - UpdatedAt: doc.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeSignature, - } - } - - page := page.NewPage(employeeDocuments, documentsPage.Cursor) - - return types.NewEmployeeDocumentConnection(page), nil -} - -// SignableDocument is the resolver for the signableDocument field. -func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { - if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, id.TenantID()) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) - document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get signable document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.EmployeeDocument{ - ID: document.ID, - Title: document.Title, - DocumentType: document.DocumentType, - CreatedAt: document.CreatedAt, - UpdatedAt: document.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeSignature, - }, nil -} - -// ApprovableDocuments is the resolver for the approvableDocuments field. -func (r *viewerResolver) ApprovableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { - if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, organizationID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ - Field: coredata.DocumentOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) - - documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list organization approvable documents", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) - for i, doc := range documentsPage.Data { - employeeDocuments[i] = &types.EmployeeDocument{ - ID: doc.ID, - Title: doc.Title, - DocumentType: doc.DocumentType, - CreatedAt: doc.CreatedAt, - UpdatedAt: doc.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeApproval, - } - } - - page := page.NewPage(employeeDocuments, documentsPage.Cursor) - - return types.NewEmployeeDocumentConnection(page), nil -} - -// ApprovableDocument is the resolver for the approvableDocument field. -func (r *viewerResolver) ApprovableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { - if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, id.TenantID()) - - identity := authn.IdentityFromContext(ctx) - - documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) - document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get approvable document", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.EmployeeDocument{ - ID: document.ID, - Title: document.Title, - DocumentType: document.DocumentType, - CreatedAt: document.CreatedAt, - UpdatedAt: document.UpdatedAt, - FilterMode: types.EmployeeDocumentFilterModeApproval, - }, nil -} - -// File returns schema.FileResolver implementation. -func (r *Resolver) File() schema.FileResolver { return &fileResolver{r} } - // Mutation returns schema.MutationResolver implementation. func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } // Query returns schema.QueryResolver implementation. func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } -// Viewer returns schema.ViewerResolver implementation. -func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } - -type fileResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } -type viewerResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/file.resolvers.go b/pkg/server/api/console/v1/file.resolvers.go new file mode 100644 index 000000000..ea9aae0fe --- /dev/null +++ b/pkg/server/api/console/v1/file.resolvers.go @@ -0,0 +1,39 @@ +package console_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" + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// DownloadURL is the resolver for the downloadUrl field. +func (r *fileResolver) DownloadURL(ctx context.Context, obj *types.File) (string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFileDownloadUrl); err != nil { + return "", err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + downloadUrl, err := prb.Files.GenerateFileTempURL(ctx, obj.ID, 60*time.Second) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot generate download URL", log.Error(err)) + return "", gqlutils.Internal(ctx) + } + + return downloadUrl, nil +} + +// File returns schema.FileResolver implementation. +func (r *Resolver) File() schema.FileResolver { return &fileResolver{r} } + +type fileResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/graphql/base.graphql b/pkg/server/api/console/v1/graphql/base.graphql index 680fcfd29..1a46793a2 100644 --- a/pkg/server/api/console/v1/graphql/base.graphql +++ b/pkg/server/api/console/v1/graphql/base.graphql @@ -1,4 +1,3 @@ -# Directives directive @goField( forceResolver: Boolean name: String @@ -12,7 +11,6 @@ directive @goModel( directive @goEnum(value: String) on ENUM_VALUE -# Scalars scalar CursorKey scalar Datetime scalar Upload @@ -20,321 +18,15 @@ scalar Duration scalar BigInt scalar EmailAddr -# Interfaces interface Node { id: ID! } -# Pagination -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: CursorKey - endCursor: CursorKey -} - -# Enums -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") -} - -enum CountryCode - @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { - AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") - AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") - AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") - AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") - AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") - AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") - AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") - AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") - AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") - AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") - AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") - AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") - AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") - AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") - AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") - AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") - BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") - BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") - BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") - BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") - BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") - BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") - BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") - BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") - BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") - BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") - BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") - BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") - BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") - BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") - BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") - BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") - BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") - BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") - BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") - BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") - BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") - CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") - CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") - CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") - CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") - CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") - CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") - CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") - CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") - CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") - CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") - CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") - CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") - CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") - CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") - CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") - CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") - CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") - CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") - CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") - DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") - DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") - DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") - DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") - DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") - DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") - EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") - EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") - EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") - EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") - ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") - ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") - ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") - EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") - FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") - FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") - FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") - FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") - FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") - FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") - GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") - GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") - GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") - GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") - GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") - GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") - GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") - GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") - GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") - GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") - GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") - GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") - GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") - GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") - GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") - GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") - GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") - GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") - HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") - HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") - HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") - HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") - HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") - HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") - ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") - IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") - IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") - IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") - IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") - IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") - IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") - IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") - IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") - IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") - JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") - JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") - JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") - JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") - KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") - KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") - KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") - KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") - KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") - KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") - KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") - KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") - KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") - KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") - KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") - LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") - LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") - LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") - LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") - LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") - LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") - LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") - LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") - LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") - LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") - LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") - MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") - MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") - MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") - ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") - MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") - MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") - MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") - MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") - ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") - MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") - MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") - MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") - MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") - MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") - MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") - MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") - MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") - MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") - MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") - MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") - MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") - MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") - MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") - NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") - NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") - NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") - NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") - NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") - NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") - NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") - NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") - NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") - NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") - NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") - NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") - OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") - PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") - PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") - PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") - PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") - PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") - PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") - PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") - PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") - PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") - PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") - PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") - PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") - PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") - PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") - QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") - RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") - RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") - RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") - RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") - RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") - SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") - SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") - SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") - SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") - SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") - SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") - SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") - SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") - SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") - SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") - SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") - SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") - SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") - SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") - SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") - SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") - ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") - SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") - SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") - SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") - SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") - TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") - TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") - TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") - TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") - TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") - TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") - TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") - TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") - TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") - TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") - TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") - TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") - TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") - TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") - TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") - TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") - UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") - UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") - UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") - US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") - UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") - UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") - VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") - VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") - VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") - VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") - VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") - VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") - VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") - WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") - WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") - YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") - YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") - ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") - ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") - ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") -} - -type File { - id: ID! - mimeType: String! - fileName: String! - size: BigInt! - downloadUrl: String! @goField(forceResolver: true) - createdAt: Datetime! - updatedAt: Datetime! -} - -# Root Types type Query { node(id: ID!): Node! viewer: Viewer! } -type Viewer { - id: ID! - - signableDocuments( - organizationId: ID! - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - ): EmployeeDocumentConnection! @goField(forceResolver: true) - - signableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) - - approvableDocuments( - organizationId: ID! - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: DocumentOrder - ): EmployeeDocumentConnection! @goField(forceResolver: true) - - approvableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) -} - type Mutation { updateOrganizationContext( input: UpdateOrganizationContextInput! diff --git a/pkg/server/api/console/v1/graphql/country_code.graphql b/pkg/server/api/console/v1/graphql/country_code.graphql new file mode 100644 index 000000000..5582496ae --- /dev/null +++ b/pkg/server/api/console/v1/graphql/country_code.graphql @@ -0,0 +1,253 @@ +enum CountryCode + @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { + AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") + AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") + AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") + AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") + AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") + AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") + AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") + AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") + AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") + AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") + AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") + AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") + AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") + AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") + AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") + AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") + BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") + BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") + BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") + BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") + BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") + BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") + BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") + BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") + BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") + BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") + BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") + BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") + BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") + BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") + BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") + BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") + BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") + BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") + BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") + BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") + BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") + CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") + CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") + CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") + CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") + CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") + CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") + CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") + CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") + CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") + CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") + CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") + CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") + CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") + CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") + CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") + CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") + CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") + CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") + CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") + DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") + DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") + DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") + DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") + DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") + DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") + EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") + EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") + EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") + EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") + ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") + ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") + ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") + EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") + FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") + FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") + FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") + FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") + FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") + FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") + GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") + GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") + GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") + GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") + GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") + GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") + GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") + GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") + GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") + GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") + GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") + GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") + GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") + GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") + GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") + GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") + GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") + GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") + HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") + HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") + HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") + HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") + HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") + HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") + ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") + IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") + IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") + IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") + IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") + IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") + IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") + IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") + IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") + IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") + JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") + JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") + JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") + JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") + KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") + KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") + KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") + KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") + KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") + KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") + KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") + KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") + KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") + KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") + KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") + LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") + LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") + LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") + LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") + LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") + LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") + LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") + LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") + LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") + LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") + LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") + MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") + MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") + MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") + ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") + MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") + MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") + MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") + MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") + ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") + MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") + MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") + MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") + MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") + MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") + MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") + MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") + MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") + MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") + MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") + MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") + MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") + MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") + MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") + NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") + NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") + NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") + NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") + NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") + NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") + NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") + NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") + NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") + NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") + NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") + NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") + OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") + PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") + PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") + PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") + PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") + PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") + PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") + PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") + PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") + PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") + PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") + PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") + PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") + PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") + PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") + QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") + RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") + RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") + RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") + RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") + RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") + SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") + SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") + SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") + SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") + SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") + SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") + SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") + SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") + SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") + SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") + SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") + SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") + SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") + SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") + SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") + SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") + ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") + SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") + SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") + SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") + SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") + TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") + TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") + TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") + TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") + TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") + TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") + TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") + TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") + TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") + TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") + TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") + TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") + TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") + TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") + TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") + TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") + UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") + UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") + UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") + US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") + UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") + UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") + VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") + VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") + VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") + VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") + VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") + VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") + VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") + WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") + WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") + YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") + YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") + ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") + ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") + ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") +} + diff --git a/pkg/server/api/console/v1/graphql/file.graphql b/pkg/server/api/console/v1/graphql/file.graphql new file mode 100644 index 000000000..ef51af51c --- /dev/null +++ b/pkg/server/api/console/v1/graphql/file.graphql @@ -0,0 +1,9 @@ +type File { + id: ID! + mimeType: String! + fileName: String! + size: BigInt! + downloadUrl: String! @goField(forceResolver: true) + createdAt: Datetime! + updatedAt: Datetime! +} diff --git a/pkg/server/api/console/v1/graphql/pagination.graphql b/pkg/server/api/console/v1/graphql/pagination.graphql new file mode 100644 index 000000000..308b8b531 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/pagination.graphql @@ -0,0 +1,12 @@ +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: CursorKey + endCursor: CursorKey +} + +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") +} diff --git a/pkg/server/api/console/v1/graphql/viewer.graphql b/pkg/server/api/console/v1/graphql/viewer.graphql new file mode 100644 index 000000000..f77412224 --- /dev/null +++ b/pkg/server/api/console/v1/graphql/viewer.graphql @@ -0,0 +1,25 @@ +type Viewer { + id: ID! + + signableDocuments( + organizationId: ID! + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + ): EmployeeDocumentConnection! @goField(forceResolver: true) + + signableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) + + approvableDocuments( + organizationId: ID! + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: DocumentOrder + ): EmployeeDocumentConnection! @goField(forceResolver: true) + + approvableDocument(id: ID!): EmployeeDocument @goField(forceResolver: true) +} diff --git a/pkg/server/api/console/v1/viewer.resolvers.go b/pkg/server/api/console/v1/viewer.resolvers.go new file mode 100644 index 000000000..872706eee --- /dev/null +++ b/pkg/server/api/console/v1/viewer.resolvers.go @@ -0,0 +1,184 @@ +package console_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/gid" + "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/probo" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/console/v1/schema" + "go.probo.inc/probo/pkg/server/api/console/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// SignableDocuments is the resolver for the signableDocuments field. +func (r *viewerResolver) SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { + if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, organizationID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) + + documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization signable documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) + for i, doc := range documentsPage.Data { + employeeDocuments[i] = &types.EmployeeDocument{ + ID: doc.ID, + Title: doc.Title, + DocumentType: doc.DocumentType, + CreatedAt: doc.CreatedAt, + UpdatedAt: doc.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeSignature, + } + } + + page := page.NewPage(employeeDocuments, documentsPage.Cursor) + + return types.NewEmployeeDocumentConnection(page), nil +} + +// SignableDocument is the resolver for the signableDocument field. +func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { + if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, id.TenantID()) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeSignature) + document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get signable document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.EmployeeDocument{ + ID: document.ID, + Title: document.Title, + DocumentType: document.DocumentType, + CreatedAt: document.CreatedAt, + UpdatedAt: document.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeSignature, + }, nil +} + +// ApprovableDocuments is the resolver for the approvableDocuments field. +func (r *viewerResolver) ApprovableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.EmployeeDocumentConnection, error) { + if err := r.authorize(ctx, organizationID, probo.ActionEmployeeDocumentList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, organizationID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{ + Field: coredata.DocumentOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.DocumentOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) + + documentsPage, err := prb.Documents.ListByOrganizationID(ctx, organizationID, cursor, documentFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list organization approvable documents", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + employeeDocuments := make([]*types.EmployeeDocument, len(documentsPage.Data)) + for i, doc := range documentsPage.Data { + employeeDocuments[i] = &types.EmployeeDocument{ + ID: doc.ID, + Title: doc.Title, + DocumentType: doc.DocumentType, + CreatedAt: doc.CreatedAt, + UpdatedAt: doc.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeApproval, + } + } + + page := page.NewPage(employeeDocuments, documentsPage.Cursor) + + return types.NewEmployeeDocumentConnection(page), nil +} + +// ApprovableDocument is the resolver for the approvableDocument field. +func (r *viewerResolver) ApprovableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.EmployeeDocument, error) { + if err := r.authorize(ctx, id, probo.ActionEmployeeDocumentGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, id.TenantID()) + + identity := authn.IdentityFromContext(ctx) + + documentFilter := coredata.NewDocumentFilter(nil).WithEmployeeIdentityID(&identity.ID, coredata.EmployeeFilterModeApproval) + document, err := prb.Documents.GetWithFilter(ctx, id, documentFilter) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get approvable document", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.EmployeeDocument{ + ID: document.ID, + Title: document.Title, + DocumentType: document.DocumentType, + CreatedAt: document.CreatedAt, + UpdatedAt: document.UpdatedAt, + FilterMode: types.EmployeeDocumentFilterModeApproval, + }, nil +} + +// Viewer returns schema.ViewerResolver implementation. +func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } + +type viewerResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/auth.resolvers.go b/pkg/server/api/trust/v1/auth.resolvers.go new file mode 100644 index 000000000..12972cb9c --- /dev/null +++ b/pkg/server/api/trust/v1/auth.resolvers.go @@ -0,0 +1,179 @@ +package trust_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/baseurl" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/saferedirect" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/compliancepage" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +// SendMagicLink is the resolver for the sendMagicLink field. +func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + baseURL := compliancepage.CompliancePageBaseURLFromContext(ctx) + + safeRedirect := saferedirect.New(saferedirect.StaticHosts(baseurl.MustParse(*baseURL).Host())) + + if input.Continue != nil { + _, ok := safeRedirect.Validate(ctx, *input.Continue) + if !ok { + return nil, gqlutils.Invalidf(ctx, "invalid continue URL") + } + } + + req := &iam.SendMagicLinkRequest{ + Email: input.Email, + CompliancePageID: &trustCenter.ID, + OrganizationID: trustCenter.OrganizationID, + URLPath: "verify-magic-link", + Continue: input.Continue, + } + + if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil { + r.logger.ErrorCtx(ctx, "cannot send magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return nil, nil +} + +// VerifyMagicLink is the resolver for the verifyMagicLink field. +func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { + session := authn.SessionFromContext(ctx) + identity := authn.IdentityFromContext(ctx) + + email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get magic link email", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var continueURL *string + + switch { + case session == nil: + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + case identity.EmailAddress != email: + 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) + } + + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errExpiredToken *iam.ErrExpiredToken + if errors.As(err, &errExpiredToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + trustCenter := compliancepage.CompliancePageFromContext(ctx) + + if _, err := r.trust.ProvisionMember(ctx, trustCenter.ID, identity.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot provision member", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + w := gqlutils.HTTPResponseWriterFromContext(ctx) + r.sessionCookie.Set(w, session) + + return &types.VerifyMagicLinkPayload{ + Continue: continueURL, + }, nil +} + +// UpdateFullName is the resolver for the updateFullName field. +func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error) { + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") + } + + identity, err := r.iam.AccountService.UpdateIdentity( + ctx, + identity.ID, + &iam.UpdateIdentityRequest{ + FullName: input.FullName, + }, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + compliancePage := compliancepage.CompliancePageFromContext(ctx) + + profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok { + r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + if profile.Source == coredata.ProfileSourceManual { + if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{ + ID: profile.ID, + FullName: identity.FullName, + AdditionalEmailAddresses: profile.AdditionalEmailAddresses, + Kind: profile.Kind, + Position: profile.Position, + ContractStartDate: &profile.ContractStartDate, + ContractEndDate: &profile.ContractEndDate, + }); err != nil { + r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.UpdateFullNamePayload{Success: true}, nil +} diff --git a/pkg/server/api/trust/v1/base.resolvers.go b/pkg/server/api/trust/v1/base.resolvers.go index 58053abb6..4472b0728 100644 --- a/pkg/server/api/trust/v1/base.resolvers.go +++ b/pkg/server/api/trust/v1/base.resolvers.go @@ -9,14 +9,10 @@ import ( "context" "errors" "strings" - "time" "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/saferedirect" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/compliancepage" "go.probo.inc/probo/pkg/server/api/trust/v1/schema" @@ -25,171 +21,6 @@ import ( "go.probo.inc/probo/pkg/trust" ) -// SendMagicLink is the resolver for the sendMagicLink field. -func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) { - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - baseURL := compliancepage.CompliancePageBaseURLFromContext(ctx) - - safeRedirect := saferedirect.New(saferedirect.StaticHosts(baseurl.MustParse(*baseURL).Host())) - - if input.Continue != nil { - _, ok := safeRedirect.Validate(ctx, *input.Continue) - if !ok { - return nil, gqlutils.Invalidf(ctx, "invalid continue URL") - } - } - - req := &iam.SendMagicLinkRequest{ - Email: input.Email, - CompliancePageID: &trustCenter.ID, - OrganizationID: trustCenter.OrganizationID, - URLPath: "verify-magic-link", - Continue: input.Continue, - } - - if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil { - r.logger.ErrorCtx(ctx, "cannot send magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return nil, nil -} - -// VerifyMagicLink is the resolver for the verifyMagicLink field. -func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { - session := authn.SessionFromContext(ctx) - identity := authn.IdentityFromContext(ctx) - - email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot get magic link email", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - var continueURL *string - - switch { - case session == nil: - var err error - identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - case identity.EmailAddress != email: - 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) - } - - var err error - identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) - if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } - - r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - - if _, err := r.trust.ProvisionMember(ctx, trustCenter.ID, identity.ID); err != nil { - r.logger.ErrorCtx(ctx, "cannot provision member", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - w := gqlutils.HTTPResponseWriterFromContext(ctx) - r.sessionCookie.Set(w, session) - - return &types.VerifyMagicLinkPayload{ - Continue: continueURL, - }, nil -} - -// UpdateFullName is the resolver for the updateFullName field. -func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error) { - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access") - } - - identity, err := r.iam.AccountService.UpdateIdentity( - ctx, - identity.ID, - &iam.UpdateIdentityRequest{ - FullName: input.FullName, - }, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - compliancePage := compliancepage.CompliancePageFromContext(ctx) - - profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID) - if err != nil { - if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok { - r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - if profile.Source == coredata.ProfileSourceManual { - if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{ - ID: profile.ID, - FullName: identity.FullName, - AdditionalEmailAddresses: profile.AdditionalEmailAddresses, - Kind: profile.Kind, - Position: profile.Position, - ContractStartDate: &profile.ContractStartDate, - ContractEndDate: &profile.ContractEndDate, - }); err != nil { - r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.UpdateFullNamePayload{Success: true}, nil -} - -// LogoURL is the resolver for the logoUrl field. -func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { - trustService := r.TrustService(ctx, obj.ID.TenantID()) - - return trustService.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) -} - // Viewer is the resolver for the viewer field. func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { identity := authn.IdentityFromContext(ctx) @@ -352,12 +183,8 @@ func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProvide // 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 mutationResolver struct{ *Resolver } -type organizationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/graphql/auth.graphql b/pkg/server/api/trust/v1/graphql/auth.graphql index d0f1d2f9f..ee56f8764 100644 --- a/pkg/server/api/trust/v1/graphql/auth.graphql +++ b/pkg/server/api/trust/v1/graphql/auth.graphql @@ -1,3 +1,12 @@ +extend type Mutation { + sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload + @session(required: OPTIONAL) + verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload + @session(required: OPTIONAL) + updateFullName(input: UpdateFullNameInput!): UpdateFullNamePayload + @session(required: PRESENT) +} + input SendMagicLinkInput { email: EmailAddr! continue: String diff --git a/pkg/server/api/trust/v1/graphql/base.graphql b/pkg/server/api/trust/v1/graphql/base.graphql index 18294645e..d7cdc2e91 100644 --- a/pkg/server/api/trust/v1/graphql/base.graphql +++ b/pkg/server/api/trust/v1/graphql/base.graphql @@ -21,291 +21,6 @@ interface Node { id: ID! } -type PageInfo { - hasNextPage: Boolean! - hasPreviousPage: Boolean! - startCursor: CursorKey - endCursor: CursorKey -} - -type Identity implements Node { - id: ID! - email: EmailAddr! - fullName: String! - emailVerified: Boolean! - createdAt: Datetime! - updatedAt: Datetime! -} - -type Organization implements Node { - id: ID! - name: String! - logoUrl: String @goField(forceResolver: true) - - description: String - websiteUrl: String - email: String - headquarterAddress: String -} - -type OIDCProviderInfo { - name: String! - loginURL: String! -} - -enum CountryCode - @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { - AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") - AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") - AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") - AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") - AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") - AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") - AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") - AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") - AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") - AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") - AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") - AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") - AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") - AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") - AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") - AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") - BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") - BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") - BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") - BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") - BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") - BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") - BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") - BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") - BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") - BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") - BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") - BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") - BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") - BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") - BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") - BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") - BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") - BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") - BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") - BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") - BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") - CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") - CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") - CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") - CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") - CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") - CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") - CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") - CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") - CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") - CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") - CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") - CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") - CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") - CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") - CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") - CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") - CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") - CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") - CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") - DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") - DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") - DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") - DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") - DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") - DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") - EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") - EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") - EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") - EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") - ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") - ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") - ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") - EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") - FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") - FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") - FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") - FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") - FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") - FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") - GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") - GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") - GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") - GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") - GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") - GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") - GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") - GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") - GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") - GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") - GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") - GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") - GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") - GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") - GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") - GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") - GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") - GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") - HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") - HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") - HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") - HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") - HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") - HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") - ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") - IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") - IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") - IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") - IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") - IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") - IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") - IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") - IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") - IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") - JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") - JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") - JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") - JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") - KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") - KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") - KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") - KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") - KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") - KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") - KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") - KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") - KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") - KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") - KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") - LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") - LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") - LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") - LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") - LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") - LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") - LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") - LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") - LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") - LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") - LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") - MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") - MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") - MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") - ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") - MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") - MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") - MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") - MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") - ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") - MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") - MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") - MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") - MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") - MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") - MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") - MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") - MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") - MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") - MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") - MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") - MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") - MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") - MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") - NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") - NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") - NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") - NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") - NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") - NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") - NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") - NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") - NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") - NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") - NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") - NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") - OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") - PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") - PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") - PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") - PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") - PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") - PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") - PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") - PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") - PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") - PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") - PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") - PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") - PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") - PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") - QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") - RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") - RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") - RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") - RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") - RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") - SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") - SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") - SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") - SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") - SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") - SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") - SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") - SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") - SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") - SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") - SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") - SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") - SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") - SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") - SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") - SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") - ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") - SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") - SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") - SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") - SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") - TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") - TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") - TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") - TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") - TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") - TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") - TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") - TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") - TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") - TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") - TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") - TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") - TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") - TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") - TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") - TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") - UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") - UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") - UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") - US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") - UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") - UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") - VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") - VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") - VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") - VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") - VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") - VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") - VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") - WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") - WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") - YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") - YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") - ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") - ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") - ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") -} - type Query { viewer: Identity node(id: ID!): Node @@ -315,11 +30,4 @@ type Query { @session(required: OPTIONAL) } -type Mutation { - sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload - @session(required: OPTIONAL) - verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload - @session(required: OPTIONAL) - updateFullName(input: UpdateFullNameInput!): UpdateFullNamePayload - @session(required: PRESENT) -} +type Mutation diff --git a/pkg/server/api/trust/v1/graphql/country_code.graphql b/pkg/server/api/trust/v1/graphql/country_code.graphql new file mode 100644 index 000000000..a25571fdd --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/country_code.graphql @@ -0,0 +1,253 @@ +enum CountryCode + @goModel(model: "go.probo.inc/probo/pkg/coredata.CountryCode") { + AD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAD") + AE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAE") + AF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAF") + AG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAG") + AI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAI") + AL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAL") + AM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAM") + AO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAO") + AQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAQ") + AR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAR") + AS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAS") + AT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAT") + AU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAU") + AW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAW") + AX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAX") + AZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeAZ") + BA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBA") + BB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBB") + BD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBD") + BE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBE") + BF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBF") + BG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBG") + BH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBH") + BI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBI") + BJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBJ") + BL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBL") + BM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBM") + BN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBN") + BO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBO") + BQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBQ") + BR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBR") + BS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBS") + BT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBT") + BV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBV") + BW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBW") + BY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBY") + BZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeBZ") + CA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCA") + CC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCC") + CD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCD") + CF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCF") + CG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCG") + CH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCH") + CI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCI") + CK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCK") + CL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCL") + CM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCM") + CN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCN") + CO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCO") + CR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCR") + CU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCU") + CV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCV") + CW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCW") + CX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCX") + CY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCY") + CZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeCZ") + DE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDE") + DJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDJ") + DK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDK") + DM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDM") + DO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDO") + DZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeDZ") + EC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEC") + EE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEE") + EG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEG") + EH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEH") + ER @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeER") + ES @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeES") + ET @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeET") + EU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeEU") + FI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFI") + FJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFJ") + FK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFK") + FM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFM") + FO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFO") + FR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeFR") + GA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGA") + GB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGB") + GD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGD") + GE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGE") + GF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGF") + GG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGG") + GH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGH") + GI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGI") + GL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGL") + GM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGM") + GN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGN") + GP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGP") + GQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGQ") + GR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGR") + GT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGT") + GU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGU") + GW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGW") + GY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeGY") + HK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHK") + HM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHM") + HN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHN") + HR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHR") + HT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHT") + HU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeHU") + ID @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeID") + IE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIE") + IL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIL") + IM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIM") + IN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIN") + IO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIO") + IQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIQ") + IR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIR") + IS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIS") + IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeIT") + JE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJE") + JM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJM") + JO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJO") + JP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeJP") + KE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKE") + KG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKG") + KH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKH") + KI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKI") + KM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKM") + KN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKN") + KP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKP") + KR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKR") + KW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKW") + KY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKY") + KZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeKZ") + LA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLA") + LB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLB") + LC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLC") + LI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLI") + LK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLK") + LR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLR") + LS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLS") + LT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLT") + LU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLU") + LV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLV") + LY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeLY") + MA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMA") + MC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMC") + MD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMD") + ME @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeME") + MF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMF") + MG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMG") + MH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMH") + MK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMK") + ML @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeML") + MM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMM") + MN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMN") + MO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMO") + MP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMP") + MQ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMQ") + MR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMR") + MS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMS") + MT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMT") + MU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMU") + MV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMV") + MW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMW") + MX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMX") + MY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMY") + MZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeMZ") + NA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNA") + NC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNC") + NE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNE") + NF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNF") + NG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNG") + NI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNI") + NL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNL") + NO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNO") + NP @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNP") + NR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNR") + NU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNU") + NZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeNZ") + OM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeOM") + PA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePA") + PE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePE") + PF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePF") + PG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePG") + PH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePH") + PK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePK") + PL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePL") + PM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePM") + PN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePN") + PR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePR") + PS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePS") + PT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePT") + PW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePW") + PY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodePY") + QA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeQA") + RE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRE") + RO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRO") + RS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRS") + RU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRU") + RW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeRW") + SA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSA") + SB @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSB") + SC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSC") + SD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSD") + SE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSE") + SG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSG") + SH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSH") + SI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSI") + SJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSJ") + SK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSK") + SL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSL") + SM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSM") + SN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSN") + SO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSO") + SR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSR") + SS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSS") + ST @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeST") + SV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSV") + SX @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSX") + SY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSY") + SZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeSZ") + TC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTC") + TD @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTD") + TF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTF") + TG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTG") + TH @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTH") + TJ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTJ") + TK @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTK") + TL @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTL") + TM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTM") + TN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTN") + TO @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTO") + TR @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTR") + TT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTT") + TV @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTV") + TW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTW") + TZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeTZ") + UA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUA") + UG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUG") + UM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUM") + US @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUS") + UY @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUY") + UZ @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeUZ") + VA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVA") + VC @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVC") + VE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVE") + VG @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVG") + VI @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVI") + VN @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVN") + VU @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeVU") + WF @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWF") + WS @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeWS") + YE @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYE") + YT @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeYT") + ZA @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZA") + ZM @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZM") + ZW @goEnum(value: "go.probo.inc/probo/pkg/coredata.CountryCodeZW") +} + diff --git a/pkg/server/api/trust/v1/graphql/identity.graphql b/pkg/server/api/trust/v1/graphql/identity.graphql new file mode 100644 index 000000000..f0c37c88b --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/identity.graphql @@ -0,0 +1,8 @@ +type Identity implements Node { + id: ID! + email: EmailAddr! + fullName: String! + emailVerified: Boolean! + createdAt: Datetime! + updatedAt: Datetime! +} diff --git a/pkg/server/api/trust/v1/graphql/oidc.graphql b/pkg/server/api/trust/v1/graphql/oidc.graphql new file mode 100644 index 000000000..016b85698 --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/oidc.graphql @@ -0,0 +1,4 @@ +type OIDCProviderInfo { + name: String! + loginURL: String! +} diff --git a/pkg/server/api/trust/v1/graphql/organization.graphql b/pkg/server/api/trust/v1/graphql/organization.graphql new file mode 100644 index 000000000..155897fc7 --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/organization.graphql @@ -0,0 +1,10 @@ +type Organization implements Node { + id: ID! + name: String! + logoUrl: String @goField(forceResolver: true) + + description: String + websiteUrl: String + email: String + headquarterAddress: String +} diff --git a/pkg/server/api/trust/v1/graphql/pagination.graphql b/pkg/server/api/trust/v1/graphql/pagination.graphql new file mode 100644 index 000000000..e70263d91 --- /dev/null +++ b/pkg/server/api/trust/v1/graphql/pagination.graphql @@ -0,0 +1,6 @@ +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: CursorKey + endCursor: CursorKey +} diff --git a/pkg/server/api/trust/v1/organization.resolvers.go b/pkg/server/api/trust/v1/organization.resolvers.go new file mode 100644 index 000000000..15f091a1b --- /dev/null +++ b/pkg/server/api/trust/v1/organization.resolvers.go @@ -0,0 +1,26 @@ +package trust_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" + "time" + + "go.probo.inc/probo/pkg/server/api/trust/v1/schema" + "go.probo.inc/probo/pkg/server/api/trust/v1/types" +) + +// LogoURL is the resolver for the logoUrl field. +func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { + trustService := r.TrustService(ctx, obj.ID.TenantID()) + + return trustService.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) +} + +// Organization returns schema.OrganizationResolver implementation. +func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } + +type organizationResolver struct{ *Resolver }