Add assume org session on api
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -59,7 +59,7 @@ type Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
signIn(input: SignInInput!): SignInPayload! @session(required: NONE)
|
signIn(input: SignInInput!): SignInPayload! @session(required: OPTIONAL)
|
||||||
signUp(input: SignUpInput!): SignUpPayload! @session(required: NONE)
|
signUp(input: SignUpInput!): SignUpPayload! @session(required: NONE)
|
||||||
signOut: SignOutPayload! @session(required: PRESENT)
|
signOut: SignOutPayload! @session(required: PRESENT)
|
||||||
signUpFromInvitation(
|
signUpFromInvitation(
|
||||||
@@ -75,6 +75,9 @@ type Mutation {
|
|||||||
@session(required: PRESENT)
|
@session(required: PRESENT)
|
||||||
changeEmail(input: ChangeEmailInput!): ChangeEmailPayload!
|
changeEmail(input: ChangeEmailInput!): ChangeEmailPayload!
|
||||||
@session(required: PRESENT)
|
@session(required: PRESENT)
|
||||||
|
assumeOrganizationSession(
|
||||||
|
input: AssumeOrganizationSessionInput!
|
||||||
|
): AssumeOrganizationSessionPayload! @session(required: PRESENT)
|
||||||
|
|
||||||
updateIdentityProfile(
|
updateIdentityProfile(
|
||||||
input: UpdateIdentityProfileInput!
|
input: UpdateIdentityProfileInput!
|
||||||
@@ -417,6 +420,12 @@ enum ProvisioningSource {
|
|||||||
SAML
|
SAML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ReauthenticationReason {
|
||||||
|
SESSION_EXPIRED
|
||||||
|
SENSITIVE_ACTION
|
||||||
|
POLICY_REQUIREMENT
|
||||||
|
}
|
||||||
|
|
||||||
enum MembershipOrderField
|
enum MembershipOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") {
|
||||||
ROLE
|
ROLE
|
||||||
@@ -579,6 +588,10 @@ input ChangeEmailInput {
|
|||||||
password: String!
|
password: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input AssumeOrganizationSessionInput {
|
||||||
|
organizationId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
input DeactivateAccountInput {
|
input DeactivateAccountInput {
|
||||||
password: String!
|
password: String!
|
||||||
}
|
}
|
||||||
@@ -709,6 +722,7 @@ input DeleteSAMLConfigurationInput {
|
|||||||
|
|
||||||
type SignInPayload {
|
type SignInPayload {
|
||||||
identity: Identity
|
identity: Identity
|
||||||
|
session: Session
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignUpPayload {
|
type SignUpPayload {
|
||||||
@@ -743,6 +757,29 @@ type ChangeEmailPayload {
|
|||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
union AssumeOrganizationSessionResult =
|
||||||
|
| OrganizationSessionCreated
|
||||||
|
| PasswordRequired
|
||||||
|
| SAMLAuthenticationRequired
|
||||||
|
|
||||||
|
type OrganizationSessionCreated {
|
||||||
|
session: Session!
|
||||||
|
membership: Membership!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasswordRequired {
|
||||||
|
reason: ReauthenticationReason!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SAMLAuthenticationRequired {
|
||||||
|
reason: ReauthenticationReason!
|
||||||
|
redirectUrl: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssumeOrganizationSessionPayload {
|
||||||
|
result: AssumeOrganizationSessionResult!
|
||||||
|
}
|
||||||
|
|
||||||
type DeactivateAccountPayload {
|
type DeactivateAccountPayload {
|
||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,10 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AssumeOrganizationSessionResult interface {
|
||||||
|
IsAssumeOrganizationSessionResult()
|
||||||
|
}
|
||||||
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
IsNode()
|
IsNode()
|
||||||
GetID() gid.GID
|
GetID() gid.GID
|
||||||
@@ -42,6 +46,14 @@ type Application struct {
|
|||||||
AvailableAccessLevels []AccessLevel `json:"availableAccessLevels"`
|
AvailableAccessLevels []AccessLevel `json:"availableAccessLevels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AssumeOrganizationSessionInput struct {
|
||||||
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssumeOrganizationSessionPayload struct {
|
||||||
|
Result AssumeOrganizationSessionResult `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
type ChangeEmailInput struct {
|
type ChangeEmailInput struct {
|
||||||
NewEmail mail.Addr `json:"newEmail"`
|
NewEmail mail.Addr `json:"newEmail"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
@@ -263,6 +275,13 @@ type Organization struct {
|
|||||||
func (Organization) IsNode() {}
|
func (Organization) IsNode() {}
|
||||||
func (this Organization) GetID() gid.GID { return this.ID }
|
func (this Organization) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type OrganizationSessionCreated struct {
|
||||||
|
Session *Session `json:"session"`
|
||||||
|
Membership *Membership `json:"membership"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OrganizationSessionCreated) IsAssumeOrganizationSessionResult() {}
|
||||||
|
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
HasNextPage bool `json:"hasNextPage"`
|
HasNextPage bool `json:"hasNextPage"`
|
||||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||||
@@ -270,6 +289,12 @@ type PageInfo struct {
|
|||||||
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PasswordRequired struct {
|
||||||
|
Reason ReauthenticationReason `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PasswordRequired) IsAssumeOrganizationSessionResult() {}
|
||||||
|
|
||||||
type Permission struct {
|
type Permission struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
@@ -365,6 +390,13 @@ type SAMLAttributeMappingsInput struct {
|
|||||||
Role *string `json:"role,omitempty"`
|
Role *string `json:"role,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SAMLAuthenticationRequired struct {
|
||||||
|
Reason ReauthenticationReason `json:"reason"`
|
||||||
|
RedirectURL string `json:"redirectUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SAMLAuthenticationRequired) IsAssumeOrganizationSessionResult() {}
|
||||||
|
|
||||||
type SAMLConfiguration struct {
|
type SAMLConfiguration struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
EmailDomain string `json:"emailDomain"`
|
EmailDomain string `json:"emailDomain"`
|
||||||
@@ -442,6 +474,7 @@ type SignInInput struct {
|
|||||||
|
|
||||||
type SignInPayload struct {
|
type SignInPayload struct {
|
||||||
Identity *Identity `json:"identity,omitempty"`
|
Identity *Identity `json:"identity,omitempty"`
|
||||||
|
Session *Session `json:"session,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignOutPayload struct {
|
type SignOutPayload struct {
|
||||||
@@ -820,6 +853,63 @@ func (e ProvisioningSource) MarshalJSON() ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReauthenticationReason string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReauthenticationReasonSessionExpired ReauthenticationReason = "SESSION_EXPIRED"
|
||||||
|
ReauthenticationReasonSensitiveAction ReauthenticationReason = "SENSITIVE_ACTION"
|
||||||
|
ReauthenticationReasonPolicyRequirement ReauthenticationReason = "POLICY_REQUIREMENT"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllReauthenticationReason = []ReauthenticationReason{
|
||||||
|
ReauthenticationReasonSessionExpired,
|
||||||
|
ReauthenticationReasonSensitiveAction,
|
||||||
|
ReauthenticationReasonPolicyRequirement,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ReauthenticationReason) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case ReauthenticationReasonSessionExpired, ReauthenticationReasonSensitiveAction, ReauthenticationReasonPolicyRequirement:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ReauthenticationReason) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ReauthenticationReason) UnmarshalGQL(v any) error {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("enums must be strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = ReauthenticationReason(str)
|
||||||
|
if !e.IsValid() {
|
||||||
|
return fmt.Errorf("%s is not a valid ReauthenticationReason", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ReauthenticationReason) MarshalGQL(w io.Writer) {
|
||||||
|
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ReauthenticationReason) UnmarshalJSON(b []byte) error {
|
||||||
|
s, err := strconv.Unquote(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.UnmarshalGQL(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ReauthenticationReason) MarshalJSON() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
e.MarshalGQL(&buf)
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
type SessionRequirement string
|
type SessionRequirement string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -215,13 +215,8 @@ func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput)
|
|||||||
)
|
)
|
||||||
|
|
||||||
return &types.SignInPayload{
|
return &types.SignInPayload{
|
||||||
Identity: &types.Identity{
|
Identity: types.NewIdentity(user),
|
||||||
ID: user.ID,
|
Session: types.NewSession(session),
|
||||||
Email: user.EmailAddress,
|
|
||||||
EmailVerified: user.EmailAddressVerified,
|
|
||||||
CreatedAt: user.CreatedAt,
|
|
||||||
UpdatedAt: user.UpdatedAt,
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +460,50 @@ func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEm
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssumeOrganizationSession is the resolver for the assumeOrganizationSession field.
|
||||||
|
func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input types.AssumeOrganizationSessionInput) (*types.AssumeOrganizationSessionPayload, error) {
|
||||||
|
rootSession := SessionFromContext(ctx)
|
||||||
|
|
||||||
|
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
var (
|
||||||
|
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||||
|
errPasswordRequired *iam.ErrPasswordRequired
|
||||||
|
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
||||||
|
)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &errMembershipNotFound):
|
||||||
|
return nil, gqlutils.NotFound(err)
|
||||||
|
|
||||||
|
case errors.As(err, &errPasswordRequired):
|
||||||
|
return &types.AssumeOrganizationSessionPayload{
|
||||||
|
Result: types.PasswordRequired{
|
||||||
|
Reason: types.ReauthenticationReason(errPasswordRequired.Reason),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case errors.As(err, &errSAMLAuthenticationRequired):
|
||||||
|
return &types.AssumeOrganizationSessionPayload{
|
||||||
|
Result: types.SAMLAuthenticationRequired{
|
||||||
|
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
||||||
|
RedirectURL: errSAMLAuthenticationRequired.RedirectURL,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("cannot assume organization session: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AssumeOrganizationSessionPayload{
|
||||||
|
Result: types.OrganizationSessionCreated{
|
||||||
|
Session: types.NewSession(childSession),
|
||||||
|
Membership: types.NewMembership(membership),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateIdentityProfile is the resolver for the updateIdentityProfile field.
|
// UpdateIdentityProfile is the resolver for the updateIdentityProfile field.
|
||||||
func (r *mutationResolver) UpdateIdentityProfile(ctx context.Context, input types.UpdateIdentityProfileInput) (*types.UpdateIdentityProfilePayload, error) {
|
func (r *mutationResolver) UpdateIdentityProfile(ctx context.Context, input types.UpdateIdentityProfileInput) (*types.UpdateIdentityProfilePayload, error) {
|
||||||
panic(fmt.Errorf("not implemented: UpdateIdentityProfile - updateIdentityProfile"))
|
panic(fmt.Errorf("not implemented: UpdateIdentityProfile - updateIdentityProfile"))
|
||||||
@@ -1061,3 +1100,15 @@ type personalAPIKeyConnectionResolver struct{ *Resolver }
|
|||||||
type queryResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
||||||
type sessionConnectionResolver struct{ *Resolver }
|
type sessionConnectionResolver struct{ *Resolver }
|
||||||
|
|
||||||
|
// !!! WARNING !!!
|
||||||
|
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
|
||||||
|
// one last chance to move it out of harms way if you want. There are two reasons this happens:
|
||||||
|
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
|
||||||
|
// it when you're done.
|
||||||
|
// - You have helper methods in this file. Move them out to keep these resolver files clean.
|
||||||
|
/*
|
||||||
|
func (r *mutationResolver) SignInWithSession(ctx context.Context, input types.SignInWithSessionInput) (*types.SignInWithSessionPayload, error) {
|
||||||
|
panic(fmt.Errorf("not implemented: SignInWithSession - signInWithSession"))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user