Rewrite identity and access management
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
94
pkg/server/api/connect/v1/api_key_middleware.go
Normal file
94
pkg/server/api/connect/v1/api_key_middleware.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/securetoken"
|
||||
)
|
||||
|
||||
var (
|
||||
apiKeyContextKey = &ctxKey{name: "api_key"}
|
||||
)
|
||||
|
||||
func APIKeyFromContext(ctx context.Context) *coredata.UserAPIKey {
|
||||
apiKey, _ := ctx.Value(apiKeyContextKey).(*coredata.UserAPIKey)
|
||||
return apiKey
|
||||
}
|
||||
|
||||
func NewAPIKeyMiddleware(svc *iam.Service) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
session := SessionFromContext(ctx)
|
||||
if session != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("api key authentication cannot be used with session authentication"))
|
||||
return
|
||||
}
|
||||
|
||||
tokenValue, err := securetoken.Get(r, "")
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
keyID, err := gid.ParseGID(tokenValue)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID)
|
||||
if err != nil {
|
||||
var errUserAPIKeyNotFound *iam.ErrUserAPIKeyNotFound
|
||||
var errUserAPIKeyExpired *iam.ErrUserAPIKeyExpired
|
||||
|
||||
if errors.As(err, &errUserAPIKeyNotFound) || errors.As(err, &errUserAPIKeyExpired) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot get user API key: %w", err))
|
||||
}
|
||||
|
||||
user, err := svc.AccountService.GetIdentity(ctx, apiKey.UserID)
|
||||
if err != nil {
|
||||
var errUserNotFound *iam.ErrUserNotFound
|
||||
if errors.As(err, &errUserNotFound) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot get user: %w", err))
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, apiKeyContextKey, apiKey)
|
||||
ctx = context.WithValue(ctx, identityContextKey, user)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
19
pkg/server/api/connect/v1/ctxkey.go
Normal file
19
pkg/server/api/connect/v1/ctxkey.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
type (
|
||||
ctxKey struct{ name string }
|
||||
)
|
||||
36
pkg/server/api/connect/v1/gqlgen.yaml
Normal file
36
pkg/server/api/connect/v1/gqlgen.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
schema: ["schema.graphql"]
|
||||
|
||||
exec:
|
||||
filename: "schema/schema.go"
|
||||
package: "schema"
|
||||
|
||||
model:
|
||||
filename: "types/types.go"
|
||||
package: "types"
|
||||
|
||||
resolver:
|
||||
layout: "follow-schema"
|
||||
dir: "."
|
||||
package: "connect_v1"
|
||||
filename_template: "v1_resolver.go"
|
||||
|
||||
autobind: []
|
||||
call_argument_directives_with_null: true
|
||||
|
||||
directives:
|
||||
mustBeAuthorized:
|
||||
skip_runtime: false
|
||||
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/gid.GIDScalar"
|
||||
Datetime:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Time"
|
||||
CursorKey:
|
||||
model:
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/cursor.CursorKeyScalar"
|
||||
EmailAddr:
|
||||
model:
|
||||
- "go.probo.inc/probo/pkg/server/gqlutils/types/mail.AddrScalar"
|
||||
102
pkg/server/api/connect/v1/graphql_handler.go
Normal file
102
pkg/server/api/connect/v1/graphql_handler.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrForbidden = &gqlerror.Error{
|
||||
Message: "You are not authorized to access this resource",
|
||||
Extensions: map[string]any{
|
||||
"code": "FORBIDDEN",
|
||||
},
|
||||
}
|
||||
|
||||
ErrUnauthorized = &gqlerror.Error{
|
||||
Message: "You are not authorized to access this resource",
|
||||
Extensions: map[string]any{
|
||||
"code": "UNAUTHORIZED",
|
||||
},
|
||||
}
|
||||
|
||||
ErrAlreadyAuthenticated = &gqlerror.Error{
|
||||
Message: "authentication not allowed for this resource/action",
|
||||
Extensions: map[string]any{
|
||||
"code": "ALREADY_AUTHENTICATED",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func SessionDirective(ctx context.Context, obj any, next graphql.Resolver, required types.SessionRequirement) (any, error) {
|
||||
session := SessionFromContext(ctx)
|
||||
|
||||
switch required {
|
||||
case types.SessionRequirementOptional:
|
||||
case types.SessionRequirementPresent:
|
||||
if session == nil {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
case types.SessionRequirementNone:
|
||||
if session != nil {
|
||||
return nil, ErrAlreadyAuthenticated
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
func IsViewerDirective(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
|
||||
identity := UserFromContext(ctx)
|
||||
resolvedIdentity, ok := obj.(*types.Identity)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("@isViewer called on non-identity object: %T", obj))
|
||||
}
|
||||
|
||||
if identity.ID != resolvedIdentity.ID {
|
||||
return nil, ErrForbidden
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
func NewGraphQLHandler(svc *iam.Service, logger *log.Logger, cookieConfig securecookie.Config) http.Handler {
|
||||
config := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
iam: svc,
|
||||
cookieConfig: cookieConfig,
|
||||
},
|
||||
Directives: schema.DirectiveRoot{
|
||||
Session: SessionDirective,
|
||||
IsViewer: IsViewerDirective,
|
||||
},
|
||||
}
|
||||
|
||||
es := schema.NewExecutableSchema(config)
|
||||
gqlh := gqlutils.NewHandler(es, logger)
|
||||
return gqlh
|
||||
}
|
||||
51
pkg/server/api/connect/v1/httpctx.go
Normal file
51
pkg/server/api/connect/v1/httpctx.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
httpResponseWriterKey = &ctxKey{name: "http_response_writer"}
|
||||
httpRequestKey = &ctxKey{name: "http_request"}
|
||||
)
|
||||
|
||||
func HTTPContextMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := WithHTTPContext(r.Context(), w, r)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func WithHTTPContext(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
|
||||
|
||||
ctx = context.WithValue(ctx, httpResponseWriterKey, w)
|
||||
ctx = context.WithValue(ctx, httpRequestKey, r)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func HTTPResponseWriterFromContext(ctx context.Context) http.ResponseWriter {
|
||||
return ctx.Value(httpResponseWriterKey).(http.ResponseWriter)
|
||||
}
|
||||
|
||||
func HTTPRequestFromContext(ctx context.Context) *http.Request {
|
||||
return ctx.Value(httpRequestKey).(*http.Request)
|
||||
}
|
||||
66
pkg/server/api/connect/v1/resolver.go
Normal file
66
pkg/server/api/connect/v1/resolver.go
Normal file
@@ -0,0 +1,66 @@
|
||||
//go:generate go run github.com/99designs/gqlgen generate
|
||||
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
)
|
||||
|
||||
type (
|
||||
Resolver struct {
|
||||
iam *iam.Service
|
||||
cookieConfig securecookie.Config
|
||||
}
|
||||
)
|
||||
|
||||
func (r *Resolver) sessionCookieConfig(maxAge time.Duration) securecookie.Config {
|
||||
return securecookie.Config{
|
||||
Name: r.cookieConfig.Name,
|
||||
Secret: r.cookieConfig.Secret,
|
||||
Secure: r.cookieConfig.Secure,
|
||||
HTTPOnly: r.cookieConfig.HTTPOnly,
|
||||
SameSite: r.cookieConfig.SameSite,
|
||||
Path: r.cookieConfig.Path,
|
||||
Domain: r.cookieConfig.Domain,
|
||||
MaxAge: int(maxAge.Seconds()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMux(logger *log.Logger, svc *iam.Service, cookieConfig securecookie.Config, baseURL *baseurl.BaseURL) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
|
||||
r.Use(HTTPContextMiddleware)
|
||||
|
||||
sessionMiddleware := NewSessionMiddleware(svc, cookieConfig)
|
||||
graphqlHandler := NewGraphQLHandler(svc, logger, cookieConfig)
|
||||
samlHandler := NewSAMLHandler(svc, cookieConfig, baseURL)
|
||||
|
||||
router := r.With(sessionMiddleware)
|
||||
|
||||
router.Handle("/graphql", graphqlHandler)
|
||||
router.Get("/saml/2.0/metadata", samlHandler.MetadataHandler)
|
||||
router.Post("/saml/2.0/consume", samlHandler.ConsumeHandler)
|
||||
router.Get("/saml/2.0/{samlConfigID}", samlHandler.LoginHandler)
|
||||
|
||||
return r
|
||||
}
|
||||
96
pkg/server/api/connect/v1/saml_handler.go
Normal file
96
pkg/server/api/connect/v1/saml_handler.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
)
|
||||
|
||||
type SAMLHandler struct {
|
||||
iam *iam.Service
|
||||
cookieConfig securecookie.Config
|
||||
baseURL *baseurl.BaseURL
|
||||
}
|
||||
|
||||
func NewSAMLHandler(iam *iam.Service, cookieConfig securecookie.Config, baseURL *baseurl.BaseURL) *SAMLHandler {
|
||||
return &SAMLHandler{iam: iam, cookieConfig: cookieConfig, baseURL: baseURL}
|
||||
}
|
||||
|
||||
func (h *SAMLHandler) MetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
metadataXML, err := h.iam.SAMLService.GenerateSpMetadata()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot generate metadata: %w", err))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/samlmetadata+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(metadataXML)
|
||||
}
|
||||
|
||||
func (h *SAMLHandler) ConsumeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("cannot parse form"))
|
||||
return
|
||||
}
|
||||
|
||||
samlResponse := r.FormValue("SAMLResponse")
|
||||
relayState := r.FormValue("RelayState")
|
||||
|
||||
configID, err := gid.ParseGID(relayState)
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("invalid relay state"))
|
||||
return
|
||||
}
|
||||
|
||||
user, membership, err := h.iam.SAMLService.HandleAssertion(ctx, samlResponse, configID)
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
session := SessionFromContext(ctx)
|
||||
if session == nil {
|
||||
h.iam.AuthService.OpenSessionWithoutPassword(ctx, user.ID, membership.OrganizationID)
|
||||
}
|
||||
|
||||
// TODO open or update the organization session
|
||||
|
||||
securecookie.Set(w, h.cookieConfig, session.ID.String())
|
||||
|
||||
redirectURL := h.baseURL.WithPath("/organizations/" + membership.OrganizationID.String()).MustString()
|
||||
|
||||
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *SAMLHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
samlConfigIDParam := chi.URLParam(r, "samlConfigID")
|
||||
if samlConfigIDParam == "" {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("missing SAML config ID"))
|
||||
return
|
||||
}
|
||||
|
||||
samlConfigID, err := gid.ParseGID(samlConfigIDParam)
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("invalid SAML config ID"))
|
||||
return
|
||||
}
|
||||
|
||||
url, err := h.iam.SAMLService.InitiateLogin(ctx, samlConfigID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot initiate SAML login: %w", err))
|
||||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), http.StatusFound)
|
||||
}
|
||||
769
pkg/server/api/connect/v1/schema.graphql
Normal file
769
pkg/server/api/connect/v1/schema.graphql
Normal file
@@ -0,0 +1,769 @@
|
||||
directive @goField(
|
||||
forceResolver: Boolean
|
||||
name: String
|
||||
omittable: Boolean
|
||||
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||
|
||||
directive @goModel(
|
||||
model: String
|
||||
models: [String!]
|
||||
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
|
||||
|
||||
directive @goEnum(value: String) on ENUM_VALUE
|
||||
|
||||
directive @session(required: SessionRequirement!) on FIELD_DEFINITION
|
||||
|
||||
directive @isViewer on FIELD_DEFINITION
|
||||
|
||||
scalar CursorKey
|
||||
scalar Datetime
|
||||
scalar Upload
|
||||
scalar EmailAddr
|
||||
|
||||
enum SessionRequirement {
|
||||
PRESENT
|
||||
NONE
|
||||
OPTIONAL
|
||||
}
|
||||
|
||||
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 SessionOrderField
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SessionOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldCreatedAt")
|
||||
EXPIRED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldExpiredAt")
|
||||
UPDATED_AT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SessionOrderFieldUpdatedAt")
|
||||
}
|
||||
|
||||
input SessionOrder {
|
||||
direction: OrderDirection!
|
||||
field: SessionOrderField!
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type Query {
|
||||
node(id: ID!): Node @session(required: PRESENT)
|
||||
viewer: Identity @session(required: PRESENT)
|
||||
checkSSOAvailability(email: String!): SSOAvailability!
|
||||
@session(required: NONE)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
signIn(input: SignInInput!): SignInPayload! @session(required: NONE)
|
||||
signUp(input: SignUpInput!): SignUpPayload! @session(required: NONE)
|
||||
signOut: SignOutPayload! @session(required: PRESENT)
|
||||
signUpFromInvitation(
|
||||
input: SignUpFromInvitationInput!
|
||||
): SignUpFromInvitationPayload! @session(required: NONE)
|
||||
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)
|
||||
|
||||
updateIdentityProfile(
|
||||
input: UpdateIdentityProfileInput!
|
||||
): UpdateIdentityProfilePayload! @session(required: PRESENT)
|
||||
|
||||
revokeSession(input: RevokeSessionInput!): RevokeSessionPayload!
|
||||
@session(required: PRESENT)
|
||||
revokeAllSessions: RevokeAllSessionsPayload! @session(required: PRESENT)
|
||||
|
||||
createPersonalAPIKey(
|
||||
input: CreatePersonalAPIKeyInput!
|
||||
): CreatePersonalAPIKeyPayload! @session(required: PRESENT)
|
||||
updatePersonalAPIKey(
|
||||
input: UpdatePersonalAPIKeyInput!
|
||||
): UpdatePersonalAPIKeyPayload! @session(required: PRESENT)
|
||||
revokePersonalAPIKey(
|
||||
input: RevokePersonalAPIKeyInput!
|
||||
): RevokePersonalAPIKeyPayload! @session(required: PRESENT)
|
||||
|
||||
createOrganization(
|
||||
input: CreateOrganizationInput!
|
||||
): CreateOrganizationPayload! @session(required: PRESENT)
|
||||
updateOrganization(
|
||||
input: UpdateOrganizationInput!
|
||||
): UpdateOrganizationPayload! @session(required: PRESENT)
|
||||
deleteOrganization(
|
||||
input: DeleteOrganizationInput!
|
||||
): DeleteOrganizationPayload! @session(required: PRESENT)
|
||||
|
||||
inviteMember(input: InviteMemberInput!): InviteMemberPayload!
|
||||
@session(required: PRESENT)
|
||||
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload!
|
||||
@session(required: PRESENT)
|
||||
removeMember(input: RemoveMemberInput!): RemoveMemberPayload!
|
||||
@session(required: PRESENT)
|
||||
|
||||
acceptInvitation(input: AcceptInvitationInput!): AcceptInvitationPayload!
|
||||
@session(required: PRESENT)
|
||||
|
||||
createSAMLConfiguration(
|
||||
input: CreateSAMLConfigurationInput!
|
||||
): CreateSAMLConfigurationPayload! @session(required: PRESENT)
|
||||
updateSAMLConfiguration(
|
||||
input: UpdateSAMLConfigurationInput!
|
||||
): UpdateSAMLConfigurationPayload! @session(required: PRESENT)
|
||||
deleteSAMLConfiguration(
|
||||
input: DeleteSAMLConfigurationInput!
|
||||
): DeleteSAMLConfigurationPayload! @session(required: PRESENT)
|
||||
}
|
||||
|
||||
type Identity implements Node {
|
||||
id: ID!
|
||||
email: EmailAddr!
|
||||
emailVerified: Boolean!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
memberships(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MembershipConnection! @goField(forceResolver: true) @isViewer
|
||||
|
||||
pendingInvitations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): InvitationConnection! @goField(forceResolver: true) @isViewer
|
||||
|
||||
sessions(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: SessionOrder
|
||||
): SessionConnection! @goField(forceResolver: true) @isViewer
|
||||
|
||||
personalAPIKeys(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): PersonalAPIKeyConnection! @goField(forceResolver: true) @isViewer
|
||||
|
||||
profileFor(organizationId: ID!): IdentityProfile @isViewer
|
||||
}
|
||||
|
||||
type IdentityProfile implements Node {
|
||||
id: ID!
|
||||
displayName: String!
|
||||
firstName: String
|
||||
lastName: String
|
||||
jobTitle: String
|
||||
department: String
|
||||
phoneNumber: String
|
||||
avatarUrl: String
|
||||
manager: IdentityProfile
|
||||
timezone: String
|
||||
locale: String
|
||||
customAttributes: [CustomAttribute!]!
|
||||
provisionedBy: ProvisioningSource!
|
||||
externalId: String
|
||||
identity: Identity!
|
||||
organization: Organization!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type CustomAttribute {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
logoUrl: String @goField(forceResolver: true)
|
||||
horizontalLogoUrl: String @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
|
||||
members(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): MembershipConnection! @goField(forceResolver: true)
|
||||
|
||||
invitations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
status: InvitationStatus
|
||||
): InvitationConnection! @goField(forceResolver: true)
|
||||
|
||||
samlConfigurations(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): SAMLConfigurationConnection! @goField(forceResolver: true)
|
||||
|
||||
availableApplications: [Application!]!
|
||||
}
|
||||
|
||||
type Membership implements Node {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
profile: IdentityProfile!
|
||||
identity: Identity! @goField(forceResolver: true)
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
permissions: [Permission!]!
|
||||
provisionedBy: ProvisioningSource!
|
||||
active: Boolean!
|
||||
lastSyncedAt: Datetime
|
||||
}
|
||||
|
||||
type Invitation implements Node {
|
||||
id: ID!
|
||||
email: EmailAddr!
|
||||
expiresAt: Datetime!
|
||||
acceptedAt: Datetime
|
||||
createdAt: Datetime!
|
||||
status: InvitationStatus!
|
||||
}
|
||||
|
||||
type InvitationProfile {
|
||||
displayName: String!
|
||||
firstName: String
|
||||
lastName: String
|
||||
jobTitle: String
|
||||
department: String
|
||||
}
|
||||
|
||||
type Session implements Node {
|
||||
id: ID!
|
||||
ipAddress: String!
|
||||
userAgent: String!
|
||||
updatedAt: Datetime!
|
||||
createdAt: Datetime!
|
||||
expiresAt: Datetime!
|
||||
}
|
||||
|
||||
type PersonalAPIKey implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
lastUsedAt: Datetime
|
||||
expiresAt: Datetime!
|
||||
createdAt: Datetime!
|
||||
scopes: [TokenScope!]!
|
||||
organizations: [Organization!]!
|
||||
}
|
||||
|
||||
type Permission implements Node {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
application: Application!
|
||||
accessLevel: AccessLevel!
|
||||
organization: Organization!
|
||||
principalType: PrincipalType!
|
||||
principalId: ID!
|
||||
}
|
||||
|
||||
type PermissionGrant {
|
||||
application: Application!
|
||||
accessLevel: AccessLevel!
|
||||
}
|
||||
|
||||
type Application {
|
||||
id: ApplicationId!
|
||||
name: String!
|
||||
description: String!
|
||||
availableAccessLevels: [AccessLevel!]!
|
||||
}
|
||||
|
||||
type SessionPolicy {
|
||||
maxSessionDurationHours: Int!
|
||||
idleTimeoutMinutes: Int!
|
||||
maxConcurrentSessions: Int
|
||||
requireReauthForSensitiveActions: Boolean!
|
||||
}
|
||||
|
||||
type SAMLConfiguration implements Node {
|
||||
id: ID!
|
||||
emailDomain: String!
|
||||
enabled: Boolean!
|
||||
enforcementPolicy: SAMLEnforcementPolicy!
|
||||
domainVerified: Boolean!
|
||||
domainVerifiedAt: Datetime
|
||||
domainVerificationToken: String
|
||||
idpEntityId: String!
|
||||
idpSsoUrl: String!
|
||||
idpCertificate: String!
|
||||
autoSignupEnabled: Boolean!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
spMetadataUrl: String!
|
||||
testLoginUrl: String!
|
||||
attributeMappings: SAMLAttributeMappings!
|
||||
defaultPermissions: [PermissionGrant!]!
|
||||
}
|
||||
|
||||
type SAMLAttributeMappings {
|
||||
email: String!
|
||||
firstName: String!
|
||||
lastName: String!
|
||||
role: String!
|
||||
}
|
||||
|
||||
type SSOAvailability {
|
||||
available: Boolean!
|
||||
samlConfigId: ID
|
||||
organizationId: ID
|
||||
}
|
||||
|
||||
enum ApplicationId {
|
||||
CONSOLE
|
||||
COMPLIANCE
|
||||
RISK
|
||||
VENDOR
|
||||
DOCUMENTS
|
||||
TRUST_CENTER
|
||||
SETTINGS
|
||||
API
|
||||
}
|
||||
|
||||
enum AccessLevel {
|
||||
READ
|
||||
WRITE
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum PrincipalType {
|
||||
IDENTITY
|
||||
SERVICE_ACCOUNT
|
||||
}
|
||||
|
||||
enum InvitationStatus
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") {
|
||||
PENDING
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending")
|
||||
ACCEPTED
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted")
|
||||
EXPIRED
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired")
|
||||
}
|
||||
|
||||
enum SAMLEnforcementPolicy
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") {
|
||||
OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff")
|
||||
OPTIONAL
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional"
|
||||
)
|
||||
REQUIRED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyRequired"
|
||||
)
|
||||
}
|
||||
|
||||
enum AuthMethod {
|
||||
PASSWORD
|
||||
SAML
|
||||
RECOVERY_CODE
|
||||
}
|
||||
|
||||
enum TokenScope {
|
||||
READ_ORGANIZATION
|
||||
WRITE_ORGANIZATION
|
||||
READ_COMPLIANCE
|
||||
WRITE_COMPLIANCE
|
||||
READ_RISK
|
||||
WRITE_RISK
|
||||
READ_VENDOR
|
||||
WRITE_VENDOR
|
||||
READ_DOCUMENTS
|
||||
WRITE_DOCUMENTS
|
||||
READ_TRUST_CENTER
|
||||
WRITE_TRUST_CENTER
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum ProvisioningSource {
|
||||
MANUAL
|
||||
INVITATION
|
||||
SAML
|
||||
}
|
||||
|
||||
type MembershipConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.MembershipConnection"
|
||||
) {
|
||||
edges: [MembershipEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type MembershipEdge {
|
||||
node: Membership!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
type InvitationConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.InvitationConnection"
|
||||
) {
|
||||
edges: [InvitationEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type InvitationEdge {
|
||||
node: Invitation!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
type SessionConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SessionConnection"
|
||||
) {
|
||||
edges: [SessionEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SessionEdge {
|
||||
node: Session!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
type PersonalAPIKeyConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.PersonalAPIKeyConnection"
|
||||
) {
|
||||
edges: [PersonalAPIKeyEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type PersonalAPIKeyEdge {
|
||||
node: PersonalAPIKey!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
type SAMLConfigurationConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.SAMLConfigurationConnection"
|
||||
) {
|
||||
edges: [SAMLConfigurationEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type SAMLConfigurationEdge {
|
||||
node: SAMLConfiguration!
|
||||
cursor: CursorKey!
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
hasNextPage: Boolean!
|
||||
hasPreviousPage: Boolean!
|
||||
startCursor: CursorKey
|
||||
endCursor: CursorKey
|
||||
}
|
||||
|
||||
input SignInInput {
|
||||
email: EmailAddr!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input SignUpInput {
|
||||
email: EmailAddr!
|
||||
password: String!
|
||||
fullName: String!
|
||||
}
|
||||
|
||||
input SignUpFromInvitationInput {
|
||||
token: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input ForgotPasswordInput {
|
||||
email: EmailAddr!
|
||||
}
|
||||
|
||||
input ResetPasswordInput {
|
||||
token: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input VerifyEmailInput {
|
||||
token: String!
|
||||
}
|
||||
|
||||
input ChangePasswordInput {
|
||||
currentPassword: String!
|
||||
newPassword: String!
|
||||
}
|
||||
|
||||
input ChangeEmailInput {
|
||||
newEmail: EmailAddr!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input DeactivateAccountInput {
|
||||
password: String!
|
||||
}
|
||||
|
||||
input DeleteAccountInput {
|
||||
password: String!
|
||||
confirmation: String!
|
||||
}
|
||||
|
||||
input UpdateIdentityProfileInput {
|
||||
membershipId: ID!
|
||||
displayName: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
jobTitle: String
|
||||
department: String
|
||||
phoneNumber: String
|
||||
timezone: String
|
||||
locale: String
|
||||
}
|
||||
|
||||
input RevokeSessionInput {
|
||||
sessionId: ID!
|
||||
}
|
||||
|
||||
input CreatePersonalAPIKeyInput {
|
||||
name: String!
|
||||
expiresAt: Datetime!
|
||||
organizationIds: [ID!]!
|
||||
}
|
||||
|
||||
input UpdatePersonalAPIKeyInput {
|
||||
tokenId: ID!
|
||||
name: String
|
||||
description: String
|
||||
}
|
||||
|
||||
input RevokePersonalAPIKeyInput {
|
||||
tokenId: ID!
|
||||
}
|
||||
|
||||
input CreateOrganizationInput {
|
||||
name: String!
|
||||
logoFile: Upload
|
||||
horizontalLogoFile: Upload
|
||||
}
|
||||
|
||||
input UpdateOrganizationInput {
|
||||
organizationId: ID!
|
||||
name: String
|
||||
logoFile: Upload @goField(omittable: true)
|
||||
horizontalLogoFile: Upload @goField(omittable: true)
|
||||
}
|
||||
|
||||
input DeleteOrganizationInput {
|
||||
organizationId: ID!
|
||||
}
|
||||
|
||||
input SessionPolicyInput {
|
||||
maxSessionDurationHours: Int
|
||||
idleTimeoutMinutes: Int
|
||||
maxConcurrentSessions: Int
|
||||
requireReauthForSensitiveActions: Boolean
|
||||
}
|
||||
|
||||
input AddIPAllowlistEntryInput {
|
||||
organizationId: ID!
|
||||
cidr: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
input RemoveIPAllowlistEntryInput {
|
||||
entryId: ID!
|
||||
}
|
||||
|
||||
input InviteMemberInput {
|
||||
organizationId: ID!
|
||||
email: EmailAddr!
|
||||
fullName: String!
|
||||
}
|
||||
|
||||
input RemoveMemberInput {
|
||||
organizationId: ID!
|
||||
membershipId: ID!
|
||||
}
|
||||
|
||||
input InvitationProfileInput {
|
||||
displayName: String!
|
||||
firstName: String
|
||||
lastName: String
|
||||
jobTitle: String
|
||||
department: String
|
||||
}
|
||||
|
||||
input AcceptInvitationInput {
|
||||
invitationId: ID!
|
||||
}
|
||||
|
||||
input DeleteInvitationInput {
|
||||
organizationId: ID!
|
||||
invitationId: ID!
|
||||
}
|
||||
|
||||
input CreateSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
emailDomain: String!
|
||||
idpEntityId: String!
|
||||
idpSsoUrl: String!
|
||||
idpCertificate: String!
|
||||
autoSignupEnabled: Boolean!
|
||||
attributeMappings: SAMLAttributeMappingsInput
|
||||
}
|
||||
|
||||
input SAMLAttributeMappingsInput {
|
||||
email: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
role: String
|
||||
}
|
||||
|
||||
input UpdateSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
samlConfigurationId: ID!
|
||||
idpEntityId: String
|
||||
idpSsoUrl: String
|
||||
idpCertificate: String
|
||||
autoSignupEnabled: Boolean
|
||||
enforcementPolicy: SAMLEnforcementPolicy
|
||||
attributeMappings: SAMLAttributeMappingsInput
|
||||
}
|
||||
|
||||
input DeleteSAMLConfigurationInput {
|
||||
organizationId: ID!
|
||||
samlConfigurationId: ID!
|
||||
}
|
||||
|
||||
type SignInPayload {
|
||||
identity: Identity
|
||||
}
|
||||
|
||||
type SignUpPayload {
|
||||
identity: Identity
|
||||
}
|
||||
|
||||
type SignOutPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type SignUpFromInvitationPayload {
|
||||
identity: Identity
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ResetPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type VerifyEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ChangePasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ChangeEmailPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type DeactivateAccountPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type DeleteAccountPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type UpdateIdentityProfilePayload {
|
||||
profile: IdentityProfile
|
||||
}
|
||||
|
||||
type RevokeSessionPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type RevokeAllSessionsPayload {
|
||||
revokedCount: Int!
|
||||
}
|
||||
|
||||
type CreatePersonalAPIKeyPayload {
|
||||
personalAPIKeyEdge: PersonalAPIKeyEdge!
|
||||
token: String!
|
||||
}
|
||||
|
||||
type UpdatePersonalAPIKeyPayload {
|
||||
personalAPIKey: PersonalAPIKey
|
||||
}
|
||||
|
||||
type RevokePersonalAPIKeyPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type CreateOrganizationPayload {
|
||||
organization: Organization
|
||||
membershipEdge: MembershipEdge!
|
||||
}
|
||||
|
||||
type UpdateOrganizationPayload {
|
||||
organization: Organization
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload {
|
||||
deletedOrganizationId: ID!
|
||||
}
|
||||
|
||||
type InviteMemberPayload {
|
||||
invitationEdge: InvitationEdge!
|
||||
}
|
||||
|
||||
type RemoveMemberPayload {
|
||||
deletedMembershipId: ID!
|
||||
}
|
||||
|
||||
type AcceptInvitationPayload {
|
||||
membershipEdge: MembershipEdge!
|
||||
}
|
||||
|
||||
type DeleteInvitationPayload {
|
||||
deletedInvitationId: ID!
|
||||
}
|
||||
|
||||
type CreateSAMLConfigurationPayload {
|
||||
samlConfigurationEdge: SAMLConfigurationEdge!
|
||||
}
|
||||
|
||||
type UpdateSAMLConfigurationPayload {
|
||||
samlConfiguration: SAMLConfiguration
|
||||
}
|
||||
|
||||
type DeleteSAMLConfigurationPayload {
|
||||
deletedSamlConfigurationId: ID!
|
||||
}
|
||||
20067
pkg/server/api/connect/v1/schema/schema.go
Normal file
20067
pkg/server/api/connect/v1/schema/schema.go
Normal file
File diff suppressed because it is too large
Load Diff
123
pkg/server/api/connect/v1/session_middleware.go
Normal file
123
pkg/server/api/connect/v1/session_middleware.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package connect_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
)
|
||||
|
||||
var (
|
||||
identityContextKey = &ctxKey{name: "identity"}
|
||||
sessionContextKey = &ctxKey{name: "session"}
|
||||
)
|
||||
|
||||
func SessionFromContext(ctx context.Context) *coredata.Session {
|
||||
session, _ := ctx.Value(sessionContextKey).(*coredata.Session)
|
||||
return session
|
||||
}
|
||||
|
||||
func UserFromContext(ctx context.Context) *coredata.User {
|
||||
user, _ := ctx.Value(identityContextKey).(*coredata.User)
|
||||
return user
|
||||
}
|
||||
|
||||
func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
apiKey := APIKeyFromContext(ctx)
|
||||
if apiKey != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, errors.New("session authentication cannot be used with API key authentication"))
|
||||
return
|
||||
}
|
||||
|
||||
cookieValue, err := securecookie.Get(r, cookieConfig)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID, err := gid.ParseGID(cookieValue)
|
||||
if err != nil {
|
||||
securecookie.Clear(w, cookieConfig)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := svc.SessionService.GetSession(ctx, sessionID)
|
||||
if err != nil {
|
||||
var errSessionNotFound *iam.ErrSessionNotFound
|
||||
var errSessionExpired *iam.ErrSessionExpired
|
||||
|
||||
if errors.As(err, &errSessionNotFound) || errors.As(err, &errSessionExpired) {
|
||||
securecookie.Clear(w, cookieConfig)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot get session: %w", err))
|
||||
}
|
||||
|
||||
user, err := svc.AccountService.GetIdentity(ctx, session.UserID)
|
||||
if err != nil {
|
||||
var errUserNotFound *iam.ErrUserNotFound
|
||||
if errors.As(err, &errUserNotFound) {
|
||||
securecookie.Clear(w, cookieConfig)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot get user: %w", err))
|
||||
}
|
||||
|
||||
userAgent := r.UserAgent()
|
||||
// TODO: will work well when no layer 7 proxy is in front of the server
|
||||
var ipAddress net.IP
|
||||
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
ipAddress = net.ParseIP(host)
|
||||
} else {
|
||||
ipAddress = net.ParseIP(r.RemoteAddr)
|
||||
}
|
||||
|
||||
err = svc.SessionService.UpdateSessionInfo(ctx, session.ID, userAgent, ipAddress)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update session info: %w", err))
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, sessionContextKey, session)
|
||||
ctx = context.WithValue(ctx, identityContextKey, user)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
|
||||
err = svc.SessionService.UpdateSessionData(ctx, session.ID, session.Data)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update session data: %w", err))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
27
pkg/server/api/connect/v1/types/identity.go
Normal file
27
pkg/server/api/connect/v1/types/identity.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import "go.probo.inc/probo/pkg/coredata"
|
||||
|
||||
func NewIdentity(identity *coredata.User) *Identity {
|
||||
return &Identity{
|
||||
ID: identity.ID,
|
||||
Email: identity.EmailAddress,
|
||||
EmailVerified: identity.EmailAddressVerified,
|
||||
CreatedAt: identity.CreatedAt,
|
||||
UpdatedAt: identity.UpdatedAt,
|
||||
}
|
||||
}
|
||||
74
pkg/server/api/connect/v1/types/invitation.go
Normal file
74
pkg/server/api/connect/v1/types/invitation.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
InvitationOrderBy OrderBy[coredata.InvitationOrderField]
|
||||
|
||||
InvitationConnection struct {
|
||||
TotalCount int
|
||||
Edges []*InvitationEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
Filters *coredata.InvitationFilter
|
||||
}
|
||||
)
|
||||
|
||||
func NewInvitationConnection(
|
||||
p *page.Page[*coredata.Invitation, coredata.InvitationOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
filters *coredata.InvitationFilter,
|
||||
) *InvitationConnection {
|
||||
edges := make([]*InvitationEdge, len(p.Data))
|
||||
for i, invitation := range p.Data {
|
||||
edges[i] = NewInvitationEdge(invitation, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &InvitationConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
Filters: filters,
|
||||
}
|
||||
}
|
||||
|
||||
func NewInvitationEdge(invitation *coredata.Invitation, orderField coredata.InvitationOrderField) *InvitationEdge {
|
||||
return &InvitationEdge{
|
||||
Node: NewInvitation(invitation),
|
||||
Cursor: invitation.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
func NewInvitation(invitation *coredata.Invitation) *Invitation {
|
||||
return &Invitation{
|
||||
ID: invitation.ID,
|
||||
Email: invitation.Email,
|
||||
ExpiresAt: invitation.ExpiresAt,
|
||||
AcceptedAt: invitation.AcceptedAt,
|
||||
CreatedAt: invitation.CreatedAt,
|
||||
Status: invitation.Status,
|
||||
}
|
||||
}
|
||||
71
pkg/server/api/connect/v1/types/membership.go
Normal file
71
pkg/server/api/connect/v1/types/membership.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
MembershipOrderBy OrderBy[coredata.MembershipOrderField]
|
||||
|
||||
MembershipConnection struct {
|
||||
TotalCount int
|
||||
Edges []*MembershipEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewMembershipConnection(
|
||||
p *page.Page[*coredata.Membership, coredata.MembershipOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
) *MembershipConnection {
|
||||
edges := make([]*MembershipEdge, len(p.Data))
|
||||
for i, membership := range p.Data {
|
||||
edges[i] = NewMembershipEdge(membership, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &MembershipConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMembershipEdge(membership *coredata.Membership, orderField coredata.MembershipOrderField) *MembershipEdge {
|
||||
return &MembershipEdge{
|
||||
Node: NewMembership(membership),
|
||||
Cursor: membership.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMembership(membership *coredata.Membership) *Membership {
|
||||
return &Membership{
|
||||
ID: membership.ID,
|
||||
CreatedAt: membership.CreatedAt,
|
||||
// Permissions: membership.Permissions,
|
||||
// ProvisionedBy: membership.ProvisionedBy,
|
||||
// Active: membership.Active,
|
||||
// LastSyncedAt: membership.LastSyncedAt,
|
||||
}
|
||||
}
|
||||
24
pkg/server/api/connect/v1/types/order_by.go
Normal file
24
pkg/server/api/connect/v1/types/order_by.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import "go.probo.inc/probo/pkg/page"
|
||||
|
||||
type (
|
||||
OrderBy[T page.OrderField] struct {
|
||||
Field T
|
||||
Direction page.OrderDirection
|
||||
}
|
||||
)
|
||||
32
pkg/server/api/connect/v1/types/organization.go
Normal file
32
pkg/server/api/connect/v1/types/organization.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
type (
|
||||
OrganizationOrderBy OrderBy[coredata.OrganizationOrderField]
|
||||
)
|
||||
|
||||
func NewOrganization(organization *coredata.Organization) *Organization {
|
||||
return &Organization{
|
||||
ID: organization.ID,
|
||||
Name: organization.Name,
|
||||
CreatedAt: organization.CreatedAt,
|
||||
UpdatedAt: organization.UpdatedAt,
|
||||
}
|
||||
}
|
||||
30
pkg/server/api/connect/v1/types/page_info.go
Normal file
30
pkg/server/api/connect/v1/types/page_info.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils/types/pageinfo"
|
||||
)
|
||||
|
||||
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
||||
data := pageinfo.NewPageInfo(p)
|
||||
return &PageInfo{
|
||||
HasNextPage: data.HasNextPage,
|
||||
HasPreviousPage: data.HasPreviousPage,
|
||||
StartCursor: data.StartCursor,
|
||||
EndCursor: data.EndCursor,
|
||||
}
|
||||
}
|
||||
69
pkg/server/api/connect/v1/types/personal_api_key.go
Normal file
69
pkg/server/api/connect/v1/types/personal_api_key.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
PersonalAPIKeyOrderBy OrderBy[coredata.UserAPIKeyOrderField]
|
||||
|
||||
PersonalAPIKeyConnection struct {
|
||||
TotalCount int
|
||||
Edges []*PersonalAPIKeyEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewPersonalAPIKeyConnection(
|
||||
p *page.Page[*coredata.UserAPIKey, coredata.UserAPIKeyOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
) *PersonalAPIKeyConnection {
|
||||
edges := make([]*PersonalAPIKeyEdge, len(p.Data))
|
||||
for i, personalAPIKey := range p.Data {
|
||||
edges[i] = NewPersonalAPIKeyEdge(personalAPIKey, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &PersonalAPIKeyConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPersonalAPIKeyEdge(personalAPIKey *coredata.UserAPIKey, orderField coredata.UserAPIKeyOrderField) *PersonalAPIKeyEdge {
|
||||
return &PersonalAPIKeyEdge{
|
||||
Node: NewPersonalAPIKey(personalAPIKey),
|
||||
Cursor: personalAPIKey.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPersonalAPIKey(personalAPIKey *coredata.UserAPIKey) *PersonalAPIKey {
|
||||
return &PersonalAPIKey{
|
||||
ID: personalAPIKey.ID,
|
||||
Name: personalAPIKey.Name,
|
||||
ExpiresAt: personalAPIKey.ExpiresAt,
|
||||
CreatedAt: personalAPIKey.CreatedAt,
|
||||
}
|
||||
}
|
||||
82
pkg/server/api/connect/v1/types/saml_configuration.go
Normal file
82
pkg/server/api/connect/v1/types/saml_configuration.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
SAMLConfigurationOrderBy OrderBy[coredata.SAMLConfigurationOrderField]
|
||||
|
||||
SAMLConfigurationConnection struct {
|
||||
TotalCount int
|
||||
Edges []*SAMLConfigurationEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewSAMLConfigurationConnection(
|
||||
p *page.Page[*coredata.SAMLConfiguration, coredata.SAMLConfigurationOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
) *SAMLConfigurationConnection {
|
||||
edges := make([]*SAMLConfigurationEdge, len(p.Data))
|
||||
for i, samlConfiguration := range p.Data {
|
||||
edges[i] = NewSAMLConfigurationEdge(samlConfiguration, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &SAMLConfigurationConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewSAMLConfigurationEdge(samlConfiguration *coredata.SAMLConfiguration, orderField coredata.SAMLConfigurationOrderField) *SAMLConfigurationEdge {
|
||||
return &SAMLConfigurationEdge{
|
||||
Node: NewSAMLConfiguration(samlConfiguration),
|
||||
Cursor: samlConfiguration.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSAMLConfiguration(samlConfiguration *coredata.SAMLConfiguration) *SAMLConfiguration {
|
||||
return &SAMLConfiguration{
|
||||
ID: samlConfiguration.ID,
|
||||
EmailDomain: samlConfiguration.EmailDomain,
|
||||
EnforcementPolicy: samlConfiguration.EnforcementPolicy,
|
||||
DomainVerified: samlConfiguration.DomainVerified,
|
||||
DomainVerifiedAt: samlConfiguration.DomainVerifiedAt,
|
||||
DomainVerificationToken: samlConfiguration.DomainVerificationToken,
|
||||
IdpEntityID: samlConfiguration.IdPEntityID,
|
||||
IdpSsoURL: samlConfiguration.IdPSsoURL,
|
||||
IdpCertificate: samlConfiguration.IdPCertificate,
|
||||
CreatedAt: samlConfiguration.CreatedAt,
|
||||
UpdatedAt: samlConfiguration.UpdatedAt,
|
||||
AttributeMappings: &SAMLAttributeMappings{
|
||||
Email: samlConfiguration.AttributeEmail,
|
||||
FirstName: samlConfiguration.AttributeFirstname,
|
||||
LastName: samlConfiguration.AttributeLastname,
|
||||
Role: samlConfiguration.AttributeRole,
|
||||
},
|
||||
}
|
||||
}
|
||||
71
pkg/server/api/connect/v1/types/session.go
Normal file
71
pkg/server/api/connect/v1/types/session.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
SessionOrderBy OrderBy[coredata.SessionOrderField]
|
||||
|
||||
SessionConnection struct {
|
||||
TotalCount int
|
||||
Edges []*SessionEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewSessionConnection(
|
||||
p *page.Page[*coredata.Session, coredata.SessionOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
) *SessionConnection {
|
||||
edges := make([]*SessionEdge, len(p.Data))
|
||||
for i, session := range p.Data {
|
||||
edges[i] = NewSessionEdge(session, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &SessionConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewSessionEdge(session *coredata.Session, orderField coredata.SessionOrderField) *SessionEdge {
|
||||
return &SessionEdge{
|
||||
Node: NewSession(session),
|
||||
Cursor: session.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSession(session *coredata.Session) *Session {
|
||||
return &Session{
|
||||
ID: session.ID,
|
||||
IPAddress: session.IPAddress.String(),
|
||||
UserAgent: session.UserAgent,
|
||||
UpdatedAt: session.UpdatedAt,
|
||||
CreatedAt: session.CreatedAt,
|
||||
ExpiresAt: session.ExpiredAt,
|
||||
}
|
||||
}
|
||||
966
pkg/server/api/connect/v1/types/types.go
Normal file
966
pkg/server/api/connect/v1/types/types.go
Normal file
@@ -0,0 +1,966 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type Node interface {
|
||||
IsNode()
|
||||
GetID() gid.GID
|
||||
}
|
||||
|
||||
type AcceptInvitationInput struct {
|
||||
InvitationID gid.GID `json:"invitationId"`
|
||||
}
|
||||
|
||||
type AcceptInvitationPayload struct {
|
||||
MembershipEdge *MembershipEdge `json:"membershipEdge"`
|
||||
}
|
||||
|
||||
type AddIPAllowlistEntryInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Cidr string `json:"cidr"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
ID ApplicationID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AvailableAccessLevels []AccessLevel `json:"availableAccessLevels"`
|
||||
}
|
||||
|
||||
type ChangeEmailInput struct {
|
||||
NewEmail mail.Addr `json:"newEmail"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ChangeEmailPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type ChangePasswordInput struct {
|
||||
CurrentPassword string `json:"currentPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type ChangePasswordPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CreateOrganizationInput struct {
|
||||
Name string `json:"name"`
|
||||
LogoFile *graphql.Upload `json:"logoFile,omitempty"`
|
||||
HorizontalLogoFile *graphql.Upload `json:"horizontalLogoFile,omitempty"`
|
||||
}
|
||||
|
||||
type CreateOrganizationPayload struct {
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
MembershipEdge *MembershipEdge `json:"membershipEdge"`
|
||||
}
|
||||
|
||||
type CreatePersonalAPIKeyInput struct {
|
||||
Name string `json:"name"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
OrganizationIds []gid.GID `json:"organizationIds"`
|
||||
}
|
||||
|
||||
type CreatePersonalAPIKeyPayload struct {
|
||||
PersonalAPIKeyEdge *PersonalAPIKeyEdge `json:"personalAPIKeyEdge"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type CreateSAMLConfigurationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
EmailDomain string `json:"emailDomain"`
|
||||
IdpEntityID string `json:"idpEntityId"`
|
||||
IdpSsoURL string `json:"idpSsoUrl"`
|
||||
IdpCertificate string `json:"idpCertificate"`
|
||||
AutoSignupEnabled bool `json:"autoSignupEnabled"`
|
||||
AttributeMappings *SAMLAttributeMappingsInput `json:"attributeMappings,omitempty"`
|
||||
}
|
||||
|
||||
type CreateSAMLConfigurationPayload struct {
|
||||
SamlConfigurationEdge *SAMLConfigurationEdge `json:"samlConfigurationEdge"`
|
||||
}
|
||||
|
||||
type CustomAttribute struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type DeactivateAccountInput struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type DeactivateAccountPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type DeleteAccountInput struct {
|
||||
Password string `json:"password"`
|
||||
Confirmation string `json:"confirmation"`
|
||||
}
|
||||
|
||||
type DeleteAccountPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type DeleteInvitationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
InvitationID gid.GID `json:"invitationId"`
|
||||
}
|
||||
|
||||
type DeleteInvitationPayload struct {
|
||||
DeletedInvitationID gid.GID `json:"deletedInvitationId"`
|
||||
}
|
||||
|
||||
type DeleteOrganizationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
}
|
||||
|
||||
type DeleteOrganizationPayload struct {
|
||||
DeletedOrganizationID gid.GID `json:"deletedOrganizationId"`
|
||||
}
|
||||
|
||||
type DeleteSAMLConfigurationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
SamlConfigurationID gid.GID `json:"samlConfigurationId"`
|
||||
}
|
||||
|
||||
type DeleteSAMLConfigurationPayload struct {
|
||||
DeletedSamlConfigurationID gid.GID `json:"deletedSamlConfigurationId"`
|
||||
}
|
||||
|
||||
type ForgotPasswordInput struct {
|
||||
Email mail.Addr `json:"email"`
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Email mail.Addr `json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Memberships *MembershipConnection `json:"memberships"`
|
||||
PendingInvitations *InvitationConnection `json:"pendingInvitations"`
|
||||
Sessions *SessionConnection `json:"sessions"`
|
||||
PersonalAPIKeys *PersonalAPIKeyConnection `json:"personalAPIKeys"`
|
||||
ProfileFor *IdentityProfile `json:"profileFor,omitempty"`
|
||||
}
|
||||
|
||||
func (Identity) IsNode() {}
|
||||
func (this Identity) GetID() gid.GID { return this.ID }
|
||||
|
||||
type IdentityProfile struct {
|
||||
ID gid.GID `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
FirstName *string `json:"firstName,omitempty"`
|
||||
LastName *string `json:"lastName,omitempty"`
|
||||
JobTitle *string `json:"jobTitle,omitempty"`
|
||||
Department *string `json:"department,omitempty"`
|
||||
PhoneNumber *string `json:"phoneNumber,omitempty"`
|
||||
AvatarURL *string `json:"avatarUrl,omitempty"`
|
||||
Manager *IdentityProfile `json:"manager,omitempty"`
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
Locale *string `json:"locale,omitempty"`
|
||||
CustomAttributes []*CustomAttribute `json:"customAttributes"`
|
||||
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
|
||||
ExternalID *string `json:"externalId,omitempty"`
|
||||
Identity *Identity `json:"identity"`
|
||||
Organization *Organization `json:"organization"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (IdentityProfile) IsNode() {}
|
||||
func (this IdentityProfile) GetID() gid.GID { return this.ID }
|
||||
|
||||
type Invitation struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Email mail.Addr `json:"email"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Status coredata.InvitationStatus `json:"status"`
|
||||
}
|
||||
|
||||
func (Invitation) IsNode() {}
|
||||
func (this Invitation) GetID() gid.GID { return this.ID }
|
||||
|
||||
type InvitationEdge struct {
|
||||
Node *Invitation `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
}
|
||||
|
||||
type InvitationProfile struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
FirstName *string `json:"firstName,omitempty"`
|
||||
LastName *string `json:"lastName,omitempty"`
|
||||
JobTitle *string `json:"jobTitle,omitempty"`
|
||||
Department *string `json:"department,omitempty"`
|
||||
}
|
||||
|
||||
type InvitationProfileInput struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
FirstName *string `json:"firstName,omitempty"`
|
||||
LastName *string `json:"lastName,omitempty"`
|
||||
JobTitle *string `json:"jobTitle,omitempty"`
|
||||
Department *string `json:"department,omitempty"`
|
||||
}
|
||||
|
||||
type InviteMemberInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Email mail.Addr `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
}
|
||||
|
||||
type InviteMemberPayload struct {
|
||||
InvitationEdge *InvitationEdge `json:"invitationEdge"`
|
||||
}
|
||||
|
||||
type Membership struct {
|
||||
ID gid.GID `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Profile *IdentityProfile `json:"profile"`
|
||||
Identity *Identity `json:"identity"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
ProvisionedBy ProvisioningSource `json:"provisionedBy"`
|
||||
Active bool `json:"active"`
|
||||
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
|
||||
}
|
||||
|
||||
func (Membership) IsNode() {}
|
||||
func (this Membership) GetID() gid.GID { return this.ID }
|
||||
|
||||
type MembershipEdge struct {
|
||||
Node *Membership `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LogoURL *string `json:"logoUrl,omitempty"`
|
||||
HorizontalLogoURL *string `json:"horizontalLogoUrl,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Members *MembershipConnection `json:"members"`
|
||||
Invitations *InvitationConnection `json:"invitations"`
|
||||
SamlConfigurations *SAMLConfigurationConnection `json:"samlConfigurations"`
|
||||
AvailableApplications []*Application `json:"availableApplications"`
|
||||
}
|
||||
|
||||
func (Organization) IsNode() {}
|
||||
func (this Organization) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PageInfo struct {
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
|
||||
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ID gid.GID `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Application *Application `json:"application"`
|
||||
AccessLevel AccessLevel `json:"accessLevel"`
|
||||
Organization *Organization `json:"organization"`
|
||||
PrincipalType PrincipalType `json:"principalType"`
|
||||
PrincipalID gid.GID `json:"principalId"`
|
||||
}
|
||||
|
||||
func (Permission) IsNode() {}
|
||||
func (this Permission) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PermissionGrant struct {
|
||||
Application *Application `json:"application"`
|
||||
AccessLevel AccessLevel `json:"accessLevel"`
|
||||
}
|
||||
|
||||
type PersonalAPIKey struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Scopes []TokenScope `json:"scopes"`
|
||||
Organizations []*Organization `json:"organizations"`
|
||||
}
|
||||
|
||||
func (PersonalAPIKey) IsNode() {}
|
||||
func (this PersonalAPIKey) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PersonalAPIKeyEdge struct {
|
||||
Node *PersonalAPIKey `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
|
||||
type RemoveIPAllowlistEntryInput struct {
|
||||
EntryID gid.GID `json:"entryId"`
|
||||
}
|
||||
|
||||
type RemoveMemberInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
MembershipID gid.GID `json:"membershipId"`
|
||||
}
|
||||
|
||||
type RemoveMemberPayload struct {
|
||||
DeletedMembershipID gid.GID `json:"deletedMembershipId"`
|
||||
}
|
||||
|
||||
type ResetPasswordInput struct {
|
||||
Token string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ResetPasswordPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type RevokeAllSessionsPayload struct {
|
||||
RevokedCount int `json:"revokedCount"`
|
||||
}
|
||||
|
||||
type RevokePersonalAPIKeyInput struct {
|
||||
TokenID gid.GID `json:"tokenId"`
|
||||
}
|
||||
|
||||
type RevokePersonalAPIKeyPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type RevokeSessionInput struct {
|
||||
SessionID gid.GID `json:"sessionId"`
|
||||
}
|
||||
|
||||
type RevokeSessionPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type SAMLAttributeMappings struct {
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type SAMLAttributeMappingsInput struct {
|
||||
Email *string `json:"email,omitempty"`
|
||||
FirstName *string `json:"firstName,omitempty"`
|
||||
LastName *string `json:"lastName,omitempty"`
|
||||
Role *string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
type SAMLConfiguration struct {
|
||||
ID gid.GID `json:"id"`
|
||||
EmailDomain string `json:"emailDomain"`
|
||||
Enabled bool `json:"enabled"`
|
||||
EnforcementPolicy coredata.SAMLEnforcementPolicy `json:"enforcementPolicy"`
|
||||
DomainVerified bool `json:"domainVerified"`
|
||||
DomainVerifiedAt *time.Time `json:"domainVerifiedAt,omitempty"`
|
||||
DomainVerificationToken *string `json:"domainVerificationToken,omitempty"`
|
||||
IdpEntityID string `json:"idpEntityId"`
|
||||
IdpSsoURL string `json:"idpSsoUrl"`
|
||||
IdpCertificate string `json:"idpCertificate"`
|
||||
AutoSignupEnabled bool `json:"autoSignupEnabled"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
SpMetadataURL string `json:"spMetadataUrl"`
|
||||
TestLoginURL string `json:"testLoginUrl"`
|
||||
AttributeMappings *SAMLAttributeMappings `json:"attributeMappings"`
|
||||
DefaultPermissions []*PermissionGrant `json:"defaultPermissions"`
|
||||
}
|
||||
|
||||
func (SAMLConfiguration) IsNode() {}
|
||||
func (this SAMLConfiguration) GetID() gid.GID { return this.ID }
|
||||
|
||||
type SAMLConfigurationEdge struct {
|
||||
Node *SAMLConfiguration `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
}
|
||||
|
||||
type SSOAvailability struct {
|
||||
Available bool `json:"available"`
|
||||
SamlConfigID *gid.GID `json:"samlConfigId,omitempty"`
|
||||
OrganizationID *gid.GID `json:"organizationId,omitempty"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID gid.GID `json:"id"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
func (Session) IsNode() {}
|
||||
func (this Session) GetID() gid.GID { return this.ID }
|
||||
|
||||
type SessionEdge struct {
|
||||
Node *Session `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
}
|
||||
|
||||
type SessionOrder struct {
|
||||
Direction page.OrderDirection `json:"direction"`
|
||||
Field coredata.SessionOrderField `json:"field"`
|
||||
}
|
||||
|
||||
type SessionPolicy struct {
|
||||
MaxSessionDurationHours int `json:"maxSessionDurationHours"`
|
||||
IdleTimeoutMinutes int `json:"idleTimeoutMinutes"`
|
||||
MaxConcurrentSessions *int `json:"maxConcurrentSessions,omitempty"`
|
||||
RequireReauthForSensitiveActions bool `json:"requireReauthForSensitiveActions"`
|
||||
}
|
||||
|
||||
type SessionPolicyInput struct {
|
||||
MaxSessionDurationHours *int `json:"maxSessionDurationHours,omitempty"`
|
||||
IdleTimeoutMinutes *int `json:"idleTimeoutMinutes,omitempty"`
|
||||
MaxConcurrentSessions *int `json:"maxConcurrentSessions,omitempty"`
|
||||
RequireReauthForSensitiveActions *bool `json:"requireReauthForSensitiveActions,omitempty"`
|
||||
}
|
||||
|
||||
type SignInInput struct {
|
||||
Email mail.Addr `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type SignInPayload struct {
|
||||
Identity *Identity `json:"identity,omitempty"`
|
||||
}
|
||||
|
||||
type SignOutPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type SignUpFromInvitationInput struct {
|
||||
Token string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type SignUpFromInvitationPayload struct {
|
||||
Identity *Identity `json:"identity,omitempty"`
|
||||
}
|
||||
|
||||
type SignUpInput struct {
|
||||
Email mail.Addr `json:"email"`
|
||||
Password string `json:"password"`
|
||||
FullName string `json:"fullName"`
|
||||
}
|
||||
|
||||
type SignUpPayload struct {
|
||||
Identity *Identity `json:"identity,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateIdentityProfileInput struct {
|
||||
MembershipID gid.GID `json:"membershipId"`
|
||||
DisplayName *string `json:"displayName,omitempty"`
|
||||
FirstName *string `json:"firstName,omitempty"`
|
||||
LastName *string `json:"lastName,omitempty"`
|
||||
JobTitle *string `json:"jobTitle,omitempty"`
|
||||
Department *string `json:"department,omitempty"`
|
||||
PhoneNumber *string `json:"phoneNumber,omitempty"`
|
||||
Timezone *string `json:"timezone,omitempty"`
|
||||
Locale *string `json:"locale,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateIdentityProfilePayload struct {
|
||||
Profile *IdentityProfile `json:"profile,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateOrganizationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
LogoFile graphql.Omittable[*graphql.Upload] `json:"logoFile,omitempty"`
|
||||
HorizontalLogoFile graphql.Omittable[*graphql.Upload] `json:"horizontalLogoFile,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateOrganizationPayload struct {
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatePersonalAPIKeyInput struct {
|
||||
TokenID gid.GID `json:"tokenId"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatePersonalAPIKeyPayload struct {
|
||||
PersonalAPIKey *PersonalAPIKey `json:"personalAPIKey,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateSAMLConfigurationInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
SamlConfigurationID gid.GID `json:"samlConfigurationId"`
|
||||
IdpEntityID *string `json:"idpEntityId,omitempty"`
|
||||
IdpSsoURL *string `json:"idpSsoUrl,omitempty"`
|
||||
IdpCertificate *string `json:"idpCertificate,omitempty"`
|
||||
AutoSignupEnabled *bool `json:"autoSignupEnabled,omitempty"`
|
||||
EnforcementPolicy *coredata.SAMLEnforcementPolicy `json:"enforcementPolicy,omitempty"`
|
||||
AttributeMappings *SAMLAttributeMappingsInput `json:"attributeMappings,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateSAMLConfigurationPayload struct {
|
||||
SamlConfiguration *SAMLConfiguration `json:"samlConfiguration,omitempty"`
|
||||
}
|
||||
|
||||
type VerifyEmailInput struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type VerifyEmailPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type AccessLevel string
|
||||
|
||||
const (
|
||||
AccessLevelRead AccessLevel = "READ"
|
||||
AccessLevelWrite AccessLevel = "WRITE"
|
||||
AccessLevelAdmin AccessLevel = "ADMIN"
|
||||
)
|
||||
|
||||
var AllAccessLevel = []AccessLevel{
|
||||
AccessLevelRead,
|
||||
AccessLevelWrite,
|
||||
AccessLevelAdmin,
|
||||
}
|
||||
|
||||
func (e AccessLevel) IsValid() bool {
|
||||
switch e {
|
||||
case AccessLevelRead, AccessLevelWrite, AccessLevelAdmin:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e AccessLevel) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *AccessLevel) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = AccessLevel(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid AccessLevel", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e AccessLevel) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *AccessLevel) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e AccessLevel) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type ApplicationID string
|
||||
|
||||
const (
|
||||
ApplicationIDConsole ApplicationID = "CONSOLE"
|
||||
ApplicationIDCompliance ApplicationID = "COMPLIANCE"
|
||||
ApplicationIDRisk ApplicationID = "RISK"
|
||||
ApplicationIDVendor ApplicationID = "VENDOR"
|
||||
ApplicationIDDocuments ApplicationID = "DOCUMENTS"
|
||||
ApplicationIDTrustCenter ApplicationID = "TRUST_CENTER"
|
||||
ApplicationIDSettings ApplicationID = "SETTINGS"
|
||||
ApplicationIDAPI ApplicationID = "API"
|
||||
)
|
||||
|
||||
var AllApplicationID = []ApplicationID{
|
||||
ApplicationIDConsole,
|
||||
ApplicationIDCompliance,
|
||||
ApplicationIDRisk,
|
||||
ApplicationIDVendor,
|
||||
ApplicationIDDocuments,
|
||||
ApplicationIDTrustCenter,
|
||||
ApplicationIDSettings,
|
||||
ApplicationIDAPI,
|
||||
}
|
||||
|
||||
func (e ApplicationID) IsValid() bool {
|
||||
switch e {
|
||||
case ApplicationIDConsole, ApplicationIDCompliance, ApplicationIDRisk, ApplicationIDVendor, ApplicationIDDocuments, ApplicationIDTrustCenter, ApplicationIDSettings, ApplicationIDAPI:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ApplicationID) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *ApplicationID) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ApplicationID(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid ApplicationId", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ApplicationID) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *ApplicationID) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e ApplicationID) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type AuthMethod string
|
||||
|
||||
const (
|
||||
AuthMethodPassword AuthMethod = "PASSWORD"
|
||||
AuthMethodSaml AuthMethod = "SAML"
|
||||
AuthMethodRecoveryCode AuthMethod = "RECOVERY_CODE"
|
||||
)
|
||||
|
||||
var AllAuthMethod = []AuthMethod{
|
||||
AuthMethodPassword,
|
||||
AuthMethodSaml,
|
||||
AuthMethodRecoveryCode,
|
||||
}
|
||||
|
||||
func (e AuthMethod) IsValid() bool {
|
||||
switch e {
|
||||
case AuthMethodPassword, AuthMethodSaml, AuthMethodRecoveryCode:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e AuthMethod) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *AuthMethod) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = AuthMethod(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid AuthMethod", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e AuthMethod) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *AuthMethod) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e AuthMethod) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type PrincipalType string
|
||||
|
||||
const (
|
||||
PrincipalTypeIdentity PrincipalType = "IDENTITY"
|
||||
PrincipalTypeServiceAccount PrincipalType = "SERVICE_ACCOUNT"
|
||||
)
|
||||
|
||||
var AllPrincipalType = []PrincipalType{
|
||||
PrincipalTypeIdentity,
|
||||
PrincipalTypeServiceAccount,
|
||||
}
|
||||
|
||||
func (e PrincipalType) IsValid() bool {
|
||||
switch e {
|
||||
case PrincipalTypeIdentity, PrincipalTypeServiceAccount:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e PrincipalType) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *PrincipalType) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = PrincipalType(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid PrincipalType", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e PrincipalType) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *PrincipalType) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e PrincipalType) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type ProvisioningSource string
|
||||
|
||||
const (
|
||||
ProvisioningSourceManual ProvisioningSource = "MANUAL"
|
||||
ProvisioningSourceInvitation ProvisioningSource = "INVITATION"
|
||||
ProvisioningSourceSaml ProvisioningSource = "SAML"
|
||||
)
|
||||
|
||||
var AllProvisioningSource = []ProvisioningSource{
|
||||
ProvisioningSourceManual,
|
||||
ProvisioningSourceInvitation,
|
||||
ProvisioningSourceSaml,
|
||||
}
|
||||
|
||||
func (e ProvisioningSource) IsValid() bool {
|
||||
switch e {
|
||||
case ProvisioningSourceManual, ProvisioningSourceInvitation, ProvisioningSourceSaml:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e ProvisioningSource) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *ProvisioningSource) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ProvisioningSource(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid ProvisioningSource", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ProvisioningSource) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *ProvisioningSource) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e ProvisioningSource) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type SessionRequirement string
|
||||
|
||||
const (
|
||||
SessionRequirementPresent SessionRequirement = "PRESENT"
|
||||
SessionRequirementNone SessionRequirement = "NONE"
|
||||
SessionRequirementOptional SessionRequirement = "OPTIONAL"
|
||||
)
|
||||
|
||||
var AllSessionRequirement = []SessionRequirement{
|
||||
SessionRequirementPresent,
|
||||
SessionRequirementNone,
|
||||
SessionRequirementOptional,
|
||||
}
|
||||
|
||||
func (e SessionRequirement) IsValid() bool {
|
||||
switch e {
|
||||
case SessionRequirementPresent, SessionRequirementNone, SessionRequirementOptional:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e SessionRequirement) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *SessionRequirement) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = SessionRequirement(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid SessionRequirement", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e SessionRequirement) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *SessionRequirement) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e SessionRequirement) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type TokenScope string
|
||||
|
||||
const (
|
||||
TokenScopeReadOrganization TokenScope = "READ_ORGANIZATION"
|
||||
TokenScopeWriteOrganization TokenScope = "WRITE_ORGANIZATION"
|
||||
TokenScopeReadCompliance TokenScope = "READ_COMPLIANCE"
|
||||
TokenScopeWriteCompliance TokenScope = "WRITE_COMPLIANCE"
|
||||
TokenScopeReadRisk TokenScope = "READ_RISK"
|
||||
TokenScopeWriteRisk TokenScope = "WRITE_RISK"
|
||||
TokenScopeReadVendor TokenScope = "READ_VENDOR"
|
||||
TokenScopeWriteVendor TokenScope = "WRITE_VENDOR"
|
||||
TokenScopeReadDocuments TokenScope = "READ_DOCUMENTS"
|
||||
TokenScopeWriteDocuments TokenScope = "WRITE_DOCUMENTS"
|
||||
TokenScopeReadTrustCenter TokenScope = "READ_TRUST_CENTER"
|
||||
TokenScopeWriteTrustCenter TokenScope = "WRITE_TRUST_CENTER"
|
||||
TokenScopeAdmin TokenScope = "ADMIN"
|
||||
)
|
||||
|
||||
var AllTokenScope = []TokenScope{
|
||||
TokenScopeReadOrganization,
|
||||
TokenScopeWriteOrganization,
|
||||
TokenScopeReadCompliance,
|
||||
TokenScopeWriteCompliance,
|
||||
TokenScopeReadRisk,
|
||||
TokenScopeWriteRisk,
|
||||
TokenScopeReadVendor,
|
||||
TokenScopeWriteVendor,
|
||||
TokenScopeReadDocuments,
|
||||
TokenScopeWriteDocuments,
|
||||
TokenScopeReadTrustCenter,
|
||||
TokenScopeWriteTrustCenter,
|
||||
TokenScopeAdmin,
|
||||
}
|
||||
|
||||
func (e TokenScope) IsValid() bool {
|
||||
switch e {
|
||||
case TokenScopeReadOrganization, TokenScopeWriteOrganization, TokenScopeReadCompliance, TokenScopeWriteCompliance, TokenScopeReadRisk, TokenScopeWriteRisk, TokenScopeReadVendor, TokenScopeWriteVendor, TokenScopeReadDocuments, TokenScopeWriteDocuments, TokenScopeReadTrustCenter, TokenScopeWriteTrustCenter, TokenScopeAdmin:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e TokenScope) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *TokenScope) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = TokenScope(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid TokenScope", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e TokenScope) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *TokenScope) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e TokenScope) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
930
pkg/server/api/connect/v1/v1_resolver.go
Normal file
930
pkg/server/api/connect/v1/v1_resolver.go
Normal file
@@ -0,0 +1,930 @@
|
||||
package connect_v1
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver
|
||||
// implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.84
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/securecookie"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
||||
)
|
||||
|
||||
// Memberships is the resolver for the memberships field.
|
||||
func (r *identityResolver) Memberships(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MembershipConnection, error) {
|
||||
pageOrderBy := page.OrderBy[coredata.MembershipOrderField]{
|
||||
Field: coredata.MembershipOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.AccountService.ListMemberships(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list memberships: %w", err))
|
||||
}
|
||||
|
||||
return types.NewMembershipConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// PendingInvitations is the resolver for the pendingInvitations field.
|
||||
func (r *identityResolver) PendingInvitations(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.InvitationConnection, error) {
|
||||
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
|
||||
Field: coredata.InvitationOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list pending invitations: %w", err))
|
||||
}
|
||||
|
||||
return types.NewInvitationConnection(page, r, obj.ID, nil), nil
|
||||
}
|
||||
|
||||
// Sessions is the resolver for the sessions field.
|
||||
func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) {
|
||||
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 {
|
||||
panic(fmt.Errorf("cannot list sessions: %w", err))
|
||||
}
|
||||
|
||||
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) {
|
||||
pageOrderBy := page.OrderBy[coredata.UserAPIKeyOrderField]{
|
||||
Field: coredata.UserAPIKeyOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list personal api keys: %w", err))
|
||||
}
|
||||
|
||||
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *organizationResolver:
|
||||
count, err := r.iam.OrganizationService.CountInvitations(ctx, obj.ParentID, obj.Filters)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count invitations: %w", err))
|
||||
}
|
||||
return count, nil
|
||||
case *identityResolver:
|
||||
count, err := r.iam.AccountService.CountPendingInvitations(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count invitations: %w", err))
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// Identity is the resolver for the identity field.
|
||||
func (r *membershipResolver) Identity(ctx context.Context, obj *types.Membership) (*types.Identity, error) {
|
||||
identity, err := r.iam.AccountService.GetIdentityForMembership(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get identity: %w", err))
|
||||
}
|
||||
|
||||
return types.NewIdentity(identity), nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *membershipResolver) Organization(ctx context.Context, obj *types.Membership) (*types.Organization, error) {
|
||||
organization, err := r.iam.OrganizationService.GetOrganizationForMembership(ctx, obj.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get organization for membership: %w", err))
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *types.MembershipConnection) (int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *identityResolver:
|
||||
count, err := r.iam.AccountService.CountMemberships(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count memberships: %w", err))
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// SignIn is the resolver for the signIn field.
|
||||
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
||||
user, session, err := r.iam.AuthService.OpenSessionWithPassword(ctx, input.Email, input.Password)
|
||||
if err != nil {
|
||||
var ErrInvalidCredentials *iam.ErrInvalidCredentials
|
||||
if errors.As(err, &ErrInvalidCredentials) {
|
||||
return nil, &gqlerror.Error{
|
||||
Message: err.Error(),
|
||||
Extensions: map[string]any{
|
||||
"code": "INVALID_CREDENTIALS",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot sign in: %w", err))
|
||||
}
|
||||
|
||||
w := HTTPResponseWriterFromContext(ctx)
|
||||
securecookie.Set(
|
||||
w,
|
||||
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
||||
session.ID.String(),
|
||||
)
|
||||
|
||||
return &types.SignInPayload{
|
||||
Identity: &types.Identity{
|
||||
ID: user.ID,
|
||||
Email: user.EmailAddress,
|
||||
EmailVerified: user.EmailAddressVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
},
|
||||
}, 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 {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot create identity with password: %w", err))
|
||||
}
|
||||
|
||||
w := HTTPResponseWriterFromContext(ctx)
|
||||
securecookie.Set(
|
||||
w,
|
||||
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
||||
session.ID.String(),
|
||||
)
|
||||
|
||||
return &types.SignUpPayload{
|
||||
Identity: types.NewIdentity(identity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignOut is the resolver for the signOut field.
|
||||
func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, error) {
|
||||
session := SessionFromContext(ctx)
|
||||
|
||||
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
||||
if err != nil {
|
||||
var ErrSessionNotFound *iam.ErrSessionNotFound
|
||||
if errors.As(err, &ErrSessionNotFound) {
|
||||
return &types.SignOutPayload{}, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot close session: %w", err))
|
||||
}
|
||||
|
||||
return &types.SignOutPayload{Success: true}, nil
|
||||
}
|
||||
|
||||
// SignUpFromInvitation is the resolver for the signUpFromInvitation field.
|
||||
func (r *mutationResolver) SignUpFromInvitation(ctx context.Context, input types.SignUpFromInvitationInput) (*types.SignUpFromInvitationPayload, error) {
|
||||
identity, session, err := r.iam.AuthService.CreateIdentityFromInvitation(
|
||||
ctx,
|
||||
&iam.CreateIdentityFromInvitationRequest{
|
||||
InvitationToken: input.Token,
|
||||
Password: input.Password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot create identity from invitation: %w", err))
|
||||
}
|
||||
|
||||
w := HTTPResponseWriterFromContext(ctx)
|
||||
securecookie.Set(
|
||||
w,
|
||||
r.sessionCookieConfig(time.Until(session.ExpiredAt)),
|
||||
session.ID.String(),
|
||||
)
|
||||
|
||||
return &types.SignUpFromInvitationPayload{
|
||||
Identity: &types.Identity{
|
||||
ID: identity.ID,
|
||||
Email: identity.EmailAddress,
|
||||
EmailVerified: identity.EmailAddressVerified,
|
||||
CreatedAt: identity.CreatedAt,
|
||||
UpdatedAt: identity.UpdatedAt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ForgotPassword is the resolver for the forgotPassword field.
|
||||
func (r *mutationResolver) ForgotPassword(ctx context.Context, input types.ForgotPasswordInput) (*types.ForgotPasswordPayload, error) {
|
||||
err := r.iam.AuthService.SendPasswordResetInstructionByEmail(
|
||||
ctx,
|
||||
input.Email,
|
||||
)
|
||||
if err != nil {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot send password reset instruction by email: %w", err))
|
||||
}
|
||||
|
||||
return &types.ForgotPasswordPayload{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResetPassword is the resolver for the resetPassword field.
|
||||
func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetPasswordInput) (*types.ResetPasswordPayload, error) {
|
||||
err := r.iam.AuthService.ResetPassword(
|
||||
ctx,
|
||||
&iam.ResetPasswordRequest{
|
||||
Token: input.Token,
|
||||
Password: input.Password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var errInvalidToken *iam.ErrInvalidToken
|
||||
if errors.As(err, &errInvalidToken) {
|
||||
return nil, gqlutils.Invalid(err, nil)
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot reset password: %w", err))
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot verify email: %w", err))
|
||||
}
|
||||
|
||||
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 := UserFromContext(ctx)
|
||||
|
||||
err := r.iam.AccountService.ChangePassword(
|
||||
ctx,
|
||||
identity.ID,
|
||||
&iam.ChangePasswordRequest{
|
||||
CurrentPassword: input.CurrentPassword,
|
||||
NewPassword: input.NewPassword,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot change password: %w", err))
|
||||
}
|
||||
|
||||
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 := UserFromContext(ctx)
|
||||
|
||||
err := r.iam.AccountService.ChangeEmail(
|
||||
ctx,
|
||||
identity.ID,
|
||||
&iam.ChangeEmailRequest{
|
||||
NewEmail: input.NewEmail,
|
||||
Password: input.Password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// TODO handle error properly here
|
||||
panic(fmt.Errorf("cannot change email: %w", err))
|
||||
}
|
||||
|
||||
return &types.ChangeEmailPayload{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateIdentityProfile is the resolver for the updateIdentityProfile field.
|
||||
func (r *mutationResolver) UpdateIdentityProfile(ctx context.Context, input types.UpdateIdentityProfileInput) (*types.UpdateIdentityProfilePayload, error) {
|
||||
panic(fmt.Errorf("not implemented: UpdateIdentityProfile - updateIdentityProfile"))
|
||||
}
|
||||
|
||||
// RevokeSession is the resolver for the revokeSession field.
|
||||
func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) {
|
||||
identity := UserFromContext(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
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot revoke session: %w", err))
|
||||
}
|
||||
|
||||
return &types.RevokeSessionPayload{Success: true}, nil
|
||||
}
|
||||
|
||||
// RevokeAllSessions is the resolver for the revokeAllSessions field.
|
||||
func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) {
|
||||
session := SessionFromContext(ctx)
|
||||
|
||||
revokedCount, err := r.iam.SessionService.RevokeAllSessions(ctx, session.ID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot revoke all sessions: %w", err))
|
||||
}
|
||||
|
||||
return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil
|
||||
}
|
||||
|
||||
// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field.
|
||||
func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) {
|
||||
identity := UserFromContext(ctx)
|
||||
|
||||
userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey(
|
||||
ctx,
|
||||
identity.ID,
|
||||
input.Name,
|
||||
input.ExpiresAt,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create personal api key: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreatePersonalAPIKeyPayload{
|
||||
PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.UserAPIKeyOrderFieldCreatedAt),
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePersonalAPIKey is the resolver for the updatePersonalAPIKey field.
|
||||
func (r *mutationResolver) UpdatePersonalAPIKey(ctx context.Context, input types.UpdatePersonalAPIKeyInput) (*types.UpdatePersonalAPIKeyPayload, error) {
|
||||
panic(fmt.Errorf("not implemented: UpdatePersonalAPIKey - updatePersonalAPIKey"))
|
||||
}
|
||||
|
||||
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
|
||||
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
|
||||
identity := UserFromContext(ctx)
|
||||
|
||||
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.TokenID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete personal api key: %w", err))
|
||||
}
|
||||
|
||||
return &types.RevokePersonalAPIKeyPayload{Success: true}, nil
|
||||
}
|
||||
|
||||
// CreateOrganization is the resolver for the createOrganization field.
|
||||
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
|
||||
identity := UserFromContext(ctx)
|
||||
|
||||
var (
|
||||
logoFile *iam.UploadedFile
|
||||
horizontalLogoFile *iam.UploadedFile
|
||||
)
|
||||
|
||||
if input.LogoFile != nil {
|
||||
logoFile = &iam.UploadedFile{
|
||||
Content: input.LogoFile.File,
|
||||
Filename: input.LogoFile.Filename,
|
||||
ContentType: input.LogoFile.ContentType,
|
||||
Size: input.LogoFile.Size,
|
||||
}
|
||||
}
|
||||
|
||||
if input.HorizontalLogoFile != nil {
|
||||
horizontalLogoFile = &iam.UploadedFile{
|
||||
Content: input.HorizontalLogoFile.File,
|
||||
Filename: input.HorizontalLogoFile.Filename,
|
||||
ContentType: input.HorizontalLogoFile.ContentType,
|
||||
Size: input.HorizontalLogoFile.Size,
|
||||
}
|
||||
}
|
||||
organization, err := r.iam.OrganizationService.CreateOrganization(
|
||||
ctx,
|
||||
identity.ID,
|
||||
&iam.CreateOrganizationRequest{
|
||||
Name: input.Name,
|
||||
LogoFile: logoFile,
|
||||
HorizontalLogoFile: horizontalLogoFile,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateOrganizationPayload{
|
||||
Organization: types.NewOrganization(organization),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateOrganization is the resolver for the updateOrganization field.
|
||||
func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) {
|
||||
organization, err := r.iam.OrganizationService.UpdateOrganization(
|
||||
ctx,
|
||||
input.OrganizationID,
|
||||
&iam.UpdateOrganizationRequest{
|
||||
Name: input.Name,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdateOrganizationPayload{
|
||||
Organization: &types.Organization{
|
||||
ID: organization.ID,
|
||||
Name: organization.Name,
|
||||
CreatedAt: organization.CreatedAt,
|
||||
UpdatedAt: organization.UpdatedAt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteOrganization is the resolver for the deleteOrganization field.
|
||||
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
|
||||
err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteOrganizationPayload{DeletedOrganizationID: input.OrganizationID}, nil
|
||||
}
|
||||
|
||||
// InviteMember is the resolver for the inviteMember field.
|
||||
func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteMemberInput) (*types.InviteMemberPayload, error) {
|
||||
invitation, err := r.iam.OrganizationService.InviteMember(
|
||||
ctx,
|
||||
input.OrganizationID,
|
||||
input.Email,
|
||||
input.FullName,
|
||||
coredata.MembershipRoleViewer,
|
||||
)
|
||||
if err != nil {
|
||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
var errMembershipAlreadyExists *iam.ErrMembershipAlreadyExists
|
||||
|
||||
if errors.As(err, &errOrganizationNotFound) {
|
||||
return nil, gqlutils.NotFound(err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errMembershipAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(err)
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot add member to organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.InviteMemberPayload{
|
||||
InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteInvitation is the resolver for the deleteInvitation field.
|
||||
func (r *mutationResolver) DeleteInvitation(ctx context.Context, input types.DeleteInvitationInput) (*types.DeleteInvitationPayload, error) {
|
||||
err := r.iam.OrganizationService.DeleteInvitation(ctx, input.OrganizationID, input.InvitationID)
|
||||
if err != nil {
|
||||
var errInvitationNotFound *iam.ErrInvitationNotFound
|
||||
var errInvitationNotPending *iam.ErrInvitationNotPending
|
||||
|
||||
if errors.As(err, &errInvitationNotFound) {
|
||||
return nil, gqlutils.NotFound(err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errInvitationNotPending) {
|
||||
return nil, gqlutils.Invalid(err, nil)
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("cannot delete invitation: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteInvitationPayload{DeletedInvitationID: input.InvitationID}, nil
|
||||
}
|
||||
|
||||
// RemoveMember is the resolver for the removeMember field.
|
||||
func (r *mutationResolver) RemoveMember(ctx context.Context, input types.RemoveMemberInput) (*types.RemoveMemberPayload, error) {
|
||||
err := r.iam.OrganizationService.RemoveMember(ctx, input.OrganizationID, input.MembershipID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot remove member from organization: %w", err))
|
||||
}
|
||||
|
||||
return &types.RemoveMemberPayload{DeletedMembershipID: input.MembershipID}, nil
|
||||
}
|
||||
|
||||
// AcceptInvitation is the resolver for the acceptInvitation field.
|
||||
func (r *mutationResolver) AcceptInvitation(ctx context.Context, input types.AcceptInvitationInput) (*types.AcceptInvitationPayload, error) {
|
||||
identity := UserFromContext(ctx)
|
||||
|
||||
membership, err := r.iam.AccountService.AcceptInvitation(ctx, identity.ID, input.InvitationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot accept invitation: %w", err))
|
||||
}
|
||||
|
||||
return &types.AcceptInvitationPayload{
|
||||
MembershipEdge: types.NewMembershipEdge(membership, coredata.MembershipOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field.
|
||||
func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) {
|
||||
req := &iam.CreateSAMLConfigurationRequest{
|
||||
EmailDomain: input.EmailDomain,
|
||||
IdPEntityID: input.IdpEntityID,
|
||||
IdPSsoURL: input.IdpSsoURL,
|
||||
IdPCertificate: input.IdpCertificate,
|
||||
AutoSignupEnabled: input.AutoSignupEnabled,
|
||||
}
|
||||
|
||||
if input.AttributeMappings != nil {
|
||||
req.AttributeEmail = input.AttributeMappings.Email
|
||||
req.AttributeFirstname = input.AttributeMappings.FirstName
|
||||
req.AttributeLastname = input.AttributeMappings.LastName
|
||||
req.AttributeRole = input.AttributeMappings.Role
|
||||
}
|
||||
|
||||
samlConfiguration, err := r.iam.OrganizationService.CreateSAMLConfiguration(
|
||||
ctx,
|
||||
input.OrganizationID,
|
||||
req,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create saml configuration: %w", err))
|
||||
}
|
||||
|
||||
return &types.CreateSAMLConfigurationPayload{
|
||||
SamlConfigurationEdge: types.NewSAMLConfigurationEdge(
|
||||
samlConfiguration,
|
||||
coredata.SAMLConfigurationOrderFieldCreatedAt,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateSAMLConfiguration is the resolver for the updateSAMLConfiguration field.
|
||||
func (r *mutationResolver) UpdateSAMLConfiguration(ctx context.Context, input types.UpdateSAMLConfigurationInput) (*types.UpdateSAMLConfigurationPayload, error) {
|
||||
req := &iam.UpdateSAMLConfigurationRequest{
|
||||
IdPEntityID: input.IdpEntityID,
|
||||
IdPSsoURL: input.IdpSsoURL,
|
||||
IdPCertificate: input.IdpCertificate,
|
||||
AutoSignupEnabled: input.AutoSignupEnabled,
|
||||
}
|
||||
|
||||
if input.AttributeMappings != nil {
|
||||
req.AttributeEmail = input.AttributeMappings.Email
|
||||
req.AttributeFirstname = input.AttributeMappings.FirstName
|
||||
req.AttributeLastname = input.AttributeMappings.LastName
|
||||
req.AttributeRole = input.AttributeMappings.Role
|
||||
}
|
||||
|
||||
samlConfiguration, err := r.iam.OrganizationService.UpdateSAMLConfiguration(
|
||||
ctx,
|
||||
input.OrganizationID,
|
||||
input.SamlConfigurationID,
|
||||
req,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update saml configuration: %w", err))
|
||||
}
|
||||
|
||||
return &types.UpdateSAMLConfigurationPayload{
|
||||
SamlConfiguration: types.NewSAMLConfiguration(samlConfiguration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteSAMLConfiguration is the resolver for the deleteSAMLConfiguration field.
|
||||
func (r *mutationResolver) DeleteSAMLConfiguration(ctx context.Context, input types.DeleteSAMLConfigurationInput) (*types.DeleteSAMLConfigurationPayload, error) {
|
||||
err := r.iam.OrganizationService.DeleteSAMLConfiguration(ctx, input.OrganizationID, input.SamlConfigurationID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot delete saml configuration: %w", err))
|
||||
}
|
||||
|
||||
return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, nil
|
||||
}
|
||||
|
||||
// LogoURL is the resolver for the logoUrl field.
|
||||
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||
presignedURL, err := r.iam.OrganizationService.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot generate logo URL: %w", err))
|
||||
}
|
||||
|
||||
return presignedURL, nil
|
||||
}
|
||||
|
||||
// HorizontalLogoURL is the resolver for the horizontalLogoUrl field.
|
||||
func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||
presignedURL, err := r.iam.OrganizationService.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot generate horizontal logo URL: %w", err))
|
||||
}
|
||||
|
||||
return presignedURL, nil
|
||||
}
|
||||
|
||||
// Members is the resolver for the members field.
|
||||
func (r *organizationResolver) Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MembershipConnection, error) {
|
||||
pageOrderBy := page.OrderBy[coredata.MembershipOrderField]{
|
||||
Field: coredata.MembershipOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.OrganizationService.ListMembers(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list memberships: %w", err))
|
||||
}
|
||||
|
||||
return types.NewMembershipConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// Invitations is the resolver for the invitations field.
|
||||
func (r *organizationResolver) Invitations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, status *coredata.InvitationStatus) (*types.InvitationConnection, error) {
|
||||
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
|
||||
Field: coredata.InvitationOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
filters := coredata.NewInvitationFilter(nil)
|
||||
if status != nil {
|
||||
filters = coredata.NewInvitationFilter([]coredata.InvitationStatus{*status})
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.OrganizationService.ListInvitations(ctx, obj.ID, cursor, filters)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list invitations: %w", err))
|
||||
}
|
||||
|
||||
return types.NewInvitationConnection(page, r, obj.ID, filters), nil
|
||||
}
|
||||
|
||||
// SamlConfigurations is the resolver for the samlConfigurations field.
|
||||
func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error) {
|
||||
pageOrderBy := page.OrderBy[coredata.SAMLConfigurationOrderField]{
|
||||
Field: coredata.SAMLConfigurationOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.OrganizationService.ListSAMLConfigurations(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list saml configurations: %w", err))
|
||||
}
|
||||
|
||||
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *identityResolver:
|
||||
count, err := r.iam.AccountService.CountPersonalAPIKeys(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count personal api keys: %w", err))
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// Node is the resolver for the node field.
|
||||
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
var loadNode func(ctx context.Context, id gid.GID) (types.Node, error)
|
||||
|
||||
user := UserFromContext(ctx)
|
||||
|
||||
r.iam.AccessManagementService.Authorize(ctx, user.ID, nil, id, iam.ActionGet)
|
||||
|
||||
switch id.EntityType() {
|
||||
case coredata.OrganizationEntityType:
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
organization, err := r.iam.OrganizationService.GetOrganization(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
case coredata.UserEntityType:
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
identity, err := r.iam.AccountService.GetIdentity(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewIdentity(identity), nil
|
||||
}
|
||||
case coredata.SessionEntityType:
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
session, err := r.iam.GetSession(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewSession(session), nil
|
||||
}
|
||||
case coredata.MembershipEntityType:
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
membership, err := r.iam.GetMembership(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewMembership(membership), nil
|
||||
}
|
||||
case coredata.InvitationEntityType:
|
||||
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||
invitation, err := r.iam.GetInvitation(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return types.NewInvitation(invitation), nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType())
|
||||
}
|
||||
|
||||
node, err := loadNode(ctx, id)
|
||||
if err != nil {
|
||||
var (
|
||||
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
errIdentityNotFound *iam.ErrUserNotFound
|
||||
errSessionNotFound *iam.ErrSessionNotFound
|
||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
errInvitationNotFound *iam.ErrInvitationNotFound
|
||||
)
|
||||
|
||||
if errors.As(err, &errOrganizationNotFound) ||
|
||||
errors.As(err, &errIdentityNotFound) ||
|
||||
errors.As(err, &errSessionNotFound) ||
|
||||
errors.As(err, &errMembershipNotFound) ||
|
||||
errors.As(err, &errInvitationNotFound) {
|
||||
return nil, gqlutils.NotFound(err)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Viewer is the resolver for the viewer field.
|
||||
func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) {
|
||||
user := UserFromContext(ctx)
|
||||
|
||||
return &types.Identity{
|
||||
ID: user.ID,
|
||||
Email: user.EmailAddress,
|
||||
EmailVerified: user.EmailAddressVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckSSOAvailability is the resolver for the checkSSOAvailability field.
|
||||
func (r *queryResolver) CheckSSOAvailability(ctx context.Context, email string) (*types.SSOAvailability, error) {
|
||||
panic(fmt.Errorf("not implemented: CheckSSOAvailability - checkSSOAvailability"))
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *organizationResolver:
|
||||
count, err := r.iam.OrganizationService.CountSAMLConfigurations(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count saml configurations: %w", err))
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (int, error) {
|
||||
switch obj.Resolver.(type) {
|
||||
case *identityResolver:
|
||||
count, err := r.iam.AccountService.CountSessions(ctx, obj.ParentID)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot count sessions: %w", err))
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// Identity returns schema.IdentityResolver implementation.
|
||||
func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} }
|
||||
|
||||
// InvitationConnection returns schema.InvitationConnectionResolver implementation.
|
||||
func (r *Resolver) InvitationConnection() schema.InvitationConnectionResolver {
|
||||
return &invitationConnectionResolver{r}
|
||||
}
|
||||
|
||||
// Membership returns schema.MembershipResolver implementation.
|
||||
func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} }
|
||||
|
||||
// MembershipConnection returns schema.MembershipConnectionResolver implementation.
|
||||
func (r *Resolver) MembershipConnection() schema.MembershipConnectionResolver {
|
||||
return &membershipConnectionResolver{r}
|
||||
}
|
||||
|
||||
// Mutation returns schema.MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
// Organization returns schema.OrganizationResolver implementation.
|
||||
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
|
||||
|
||||
// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation.
|
||||
func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver {
|
||||
return &personalAPIKeyConnectionResolver{r}
|
||||
}
|
||||
|
||||
// Query returns schema.QueryResolver implementation.
|
||||
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
// SAMLConfigurationConnection returns schema.SAMLConfigurationConnectionResolver implementation.
|
||||
func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnectionResolver {
|
||||
return &sAMLConfigurationConnectionResolver{r}
|
||||
}
|
||||
|
||||
// SessionConnection returns schema.SessionConnectionResolver implementation.
|
||||
func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
|
||||
return &sessionConnectionResolver{r}
|
||||
}
|
||||
|
||||
type identityResolver struct{ *Resolver }
|
||||
type invitationConnectionResolver struct{ *Resolver }
|
||||
type membershipResolver struct{ *Resolver }
|
||||
type membershipConnectionResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type organizationResolver struct{ *Resolver }
|
||||
type personalAPIKeyConnectionResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
||||
type sessionConnectionResolver struct{ *Resolver }
|
||||
Reference in New Issue
Block a user