Implement activate account page
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<e920b2bbd380e12014a100bfb963c8f7>>
|
||||
* @generated SignedSource<<b79143becc90a4776f321d0e91db49bd>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ActivateAccountInput = {
|
||||
password: string;
|
||||
password?: string | null | undefined;
|
||||
token: string;
|
||||
};
|
||||
export type ActivateAccountPageMutation$variables = {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { formatError, type GraphQLError } from "@probo/helpers";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Field, useToast } from "@probo/ui";
|
||||
import { useToast } from "@probo/ui";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useMutation } from "react-relay";
|
||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ActivateAccountPageMutation } from "#/__generated__/iam/ActivateAccountPageMutation.graphql";
|
||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||
|
||||
const activateAccountMutation = graphql`
|
||||
mutation ActivateAccountPageMutation(
|
||||
@@ -22,64 +21,45 @@ const activateAccountMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const schema = z.object({
|
||||
password: z.string().min(8),
|
||||
fullName: z.string().min(2),
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof schema>;
|
||||
|
||||
export default function ActivateAccountPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const fullNameFromParams = searchParams.get("fullName") || "";
|
||||
const submittedRef = useRef<boolean>(false);
|
||||
|
||||
usePageTitle(__("Sign up"));
|
||||
|
||||
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
password: "",
|
||||
fullName: fullNameFromParams,
|
||||
},
|
||||
});
|
||||
usePageTitle(__("Activate Account"));
|
||||
|
||||
const [activateAccount] = useMutation<ActivateAccountPageMutation>(activateAccountMutation);
|
||||
|
||||
const onSubmit = (data: FormData) => {
|
||||
const token = searchParams.get("token");
|
||||
if (!token) {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: __("Invalid or missing invitation token"),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handleActivateAccount = useCallback((token: string) => {
|
||||
if (submittedRef.current) return;
|
||||
|
||||
activateAccount({
|
||||
variables: {
|
||||
input: {
|
||||
token,
|
||||
password: data.password,
|
||||
fullName: data.fullName,
|
||||
},
|
||||
input: { token },
|
||||
},
|
||||
onCompleted: (_, e) => {
|
||||
if (e) {
|
||||
onCompleted: (_, errors: GraphQLError[] | null) => {
|
||||
if (errors) {
|
||||
for (const err of errors) {
|
||||
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
||||
window.location.href = "/";
|
||||
return;
|
||||
}
|
||||
}
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
description: formatError(__("Signup failed"), e),
|
||||
title: __("Activation failed"),
|
||||
description: formatError(__("Activation failed"), errors),
|
||||
variant: "error",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Account created successfully. Please accept your invitation to join the organization.",
|
||||
"Account activated successfully.",
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
@@ -87,60 +67,37 @@ export default function ActivateAccountPage() {
|
||||
},
|
||||
onError: (e) => {
|
||||
toast({
|
||||
title: __("Signup failed"),
|
||||
title: __("Activation failed"),
|
||||
description: e.message,
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [__, toast, activateAccount, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get("token");
|
||||
if (!submittedRef.current && token) {
|
||||
void handleActivateAccount(token.trim());
|
||||
submittedRef.current = true;
|
||||
}
|
||||
}, [handleActivateAccount, searchParams]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
|
||||
<div className="space-y-2 text-center">
|
||||
<h1 className="text-3xl font-bold">{__("Create your account")}</h1>
|
||||
<h1 className="text-3xl font-bold">{__("Account Activation")}</h1>
|
||||
<p className="text-txt-tertiary">
|
||||
{__("Set your password to join the organization")}
|
||||
{__("Activating your account…")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={e => void handleSubmit(onSubmit)(e)} className="space-y-4">
|
||||
<Field
|
||||
label={__("Full Name")}
|
||||
type="text"
|
||||
placeholder={__("John Doe")}
|
||||
{...register("fullName")}
|
||||
required
|
||||
error={formState.errors.fullName?.message}
|
||||
/>
|
||||
|
||||
<Field
|
||||
label={__("Password")}
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
{...register("password")}
|
||||
required
|
||||
error={formState.errors.password?.message}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-xs h-10 mx-auto mt-6" disabled={formState.isLoading}>
|
||||
{formState.isLoading
|
||||
? __("Creating account...")
|
||||
: __("Create account")}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-txt-tertiary">
|
||||
{__("Already have an account?")}
|
||||
{" "}
|
||||
<Link
|
||||
to="/auth/login"
|
||||
className="underline text-txt-primary hover:text-txt-secondary"
|
||||
>
|
||||
{__("Log in here")}
|
||||
</Link>
|
||||
</p>
|
||||
<div className="text-center mt-6 text-sm text-txt-secondary">
|
||||
<Link
|
||||
to="/auth/login"
|
||||
className="underline hover:text-txt-primary"
|
||||
>
|
||||
{__("Go back")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,17 +6,17 @@ export const Invitation = () => {
|
||||
return (
|
||||
<EmailLayout subject={'Invitation to join {{.OrganizationName}} on Probo'}>
|
||||
<Text style={bodyText}>
|
||||
You have been invited to join organization <strong>{'{{.OrganizationName}}'}</strong> on Probo. Click the button below to accept the invitation:
|
||||
You have been invited to join organization <strong>{'{{.OrganizationName}}'}</strong> on Probo. Click the button below to activate your account:
|
||||
</Text>
|
||||
|
||||
<Section style={buttonContainer}>
|
||||
<Button style={button} href={'{{.InvitationUrl}}'}>
|
||||
Accept Invitation
|
||||
Activate Account
|
||||
</Button>
|
||||
</Section>
|
||||
|
||||
<Text style={footerText}>
|
||||
If you don't want to accept the invitation, you can ignore this email.
|
||||
If you don't want to do so, you can ignore this email.
|
||||
</Text>
|
||||
</EmailLayout>
|
||||
);
|
||||
|
||||
@@ -2,11 +2,11 @@ Probo
|
||||
|
||||
Hi {{.RecipientFullName}},
|
||||
|
||||
You have been invited to join organization {{.OrganizationName}} on Probo. Click the link below to accept the invitation:
|
||||
You have been invited to join organization {{.OrganizationName}} on Probo. Click the link below to activate your account:
|
||||
|
||||
{{.InvitationUrl}}
|
||||
|
||||
If you don't want to accept the invitation, you can ignore this email.
|
||||
If you don't want to do so, you can ignore this email.
|
||||
|
||||
{{.SenderCompanyHeadquarterAddress}}
|
||||
Powered By Probo
|
||||
|
||||
@@ -293,7 +293,6 @@ func (i *Invitations) ExpireByUserID(
|
||||
expires_at = NOW()
|
||||
WHERE
|
||||
user_id = @user_id
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
AND %s
|
||||
`
|
||||
|
||||
@@ -47,7 +47,6 @@ type (
|
||||
|
||||
CreateIdentityFromInvitationRequest struct {
|
||||
InvitationToken string
|
||||
Password string
|
||||
}
|
||||
|
||||
LoadOrCreateIdentityRequest struct {
|
||||
@@ -92,7 +91,6 @@ func (req CreateIdentityFromInvitationRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(req.InvitationToken, "invitationToken", validator.NotEmpty())
|
||||
v.Check(req.Password, "password", PasswordValidator())
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
@@ -153,11 +151,6 @@ func (s *AuthService) ActivateAccount(
|
||||
now = time.Now()
|
||||
)
|
||||
|
||||
hashedPassword, err := s.hp.HashPassword([]byte(req.Password))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot hash password: %w", err)
|
||||
}
|
||||
|
||||
err = s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
@@ -197,7 +190,6 @@ func (s *AuthService) ActivateAccount(
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
identity.HashedPassword = hashedPassword
|
||||
identity.EmailAddressVerified = true
|
||||
identity.UpdatedAt = now
|
||||
|
||||
|
||||
@@ -326,15 +326,15 @@ func (e ErrInvitationNotDeleted) Error() string {
|
||||
return fmt.Sprintf("cannot delete invitation %q in %q status", e.InvitationID, e.Status)
|
||||
}
|
||||
|
||||
type ErrPasswordRequired struct {
|
||||
type ErrPasswordAuthenticationRequired struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
func NewPasswordRequiredError(reason string) *ErrPasswordRequired {
|
||||
return &ErrPasswordRequired{Reason: reason}
|
||||
func NewPasswordAuthenticationRequiredError(reason string) *ErrPasswordAuthenticationRequired {
|
||||
return &ErrPasswordAuthenticationRequired{Reason: reason}
|
||||
}
|
||||
|
||||
func (e *ErrPasswordRequired) Error() string {
|
||||
func (e *ErrPasswordAuthenticationRequired) Error() string {
|
||||
return fmt.Sprintf("password authentication required: %s", e.Reason)
|
||||
}
|
||||
|
||||
|
||||
@@ -573,16 +573,23 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
return fmt.Errorf("cannot load SAML configuration: %w", err)
|
||||
}
|
||||
|
||||
if err == nil && samlConfig.EnforcementPolicy == coredata.SAMLEnforcementPolicyRequired {
|
||||
if rootSession.AuthMethod != coredata.AuthMethodSAML {
|
||||
return NewSAMLAuthenticationRequiredError("policy_requirement")
|
||||
if err == nil {
|
||||
switch samlConfig.EnforcementPolicy {
|
||||
case coredata.SAMLEnforcementPolicyRequired:
|
||||
if rootSession.AuthMethod != coredata.AuthMethodSAML {
|
||||
return NewSAMLAuthenticationRequiredError("policy_requirement")
|
||||
}
|
||||
case coredata.SAMLEnforcementPolicyOptional:
|
||||
// SAML is optional: both PASSWORD and SAML root sessions are allowed.
|
||||
}
|
||||
} else {
|
||||
switch rootSession.AuthMethod {
|
||||
case coredata.AuthMethodPassword:
|
||||
case coredata.AuthMethodSAML:
|
||||
// No (or non-required) SAML configuration: require a password-authenticated or magic-link root session
|
||||
// (eg. when switching into a password-based org from a SAML login)
|
||||
return NewPasswordAuthenticationRequiredError("password_authentication_required")
|
||||
}
|
||||
} else if err == nil && samlConfig.EnforcementPolicy == coredata.SAMLEnforcementPolicyOptional {
|
||||
// SAML is optional: both PASSWORD and SAML root sessions are allowed.
|
||||
} else if rootSession.AuthMethod != coredata.AuthMethodPassword {
|
||||
// No (or non-required) SAML configuration: require a password-authenticated root session
|
||||
// (eg. when switching into a password-based org from a SAML login).
|
||||
return NewPasswordRequiredError("password_authentication_required")
|
||||
}
|
||||
|
||||
tenantID := scope.GetTenantID()
|
||||
|
||||
@@ -141,6 +141,7 @@ type Identity implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ProfileOrder
|
||||
filter: ProfileFilter
|
||||
): ProfileConnection @goField(forceResolver: true)
|
||||
|
||||
sessions(
|
||||
@@ -512,6 +513,11 @@ enum ProfileOrderField
|
||||
)
|
||||
}
|
||||
|
||||
input ProfileFilter {
|
||||
excludeContractEnded: Boolean
|
||||
state: ProfileState
|
||||
}
|
||||
|
||||
input ProfileOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileOrderBy"
|
||||
@@ -628,7 +634,6 @@ input SignUpInput {
|
||||
|
||||
input ActivateAccountInput {
|
||||
token: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input ForgotPasswordInput {
|
||||
@@ -794,6 +799,10 @@ type ActivateAccountPayload {
|
||||
profile: Profile
|
||||
}
|
||||
|
||||
type CreatePasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ type ComplexityRoot struct {
|
||||
Organization func(childComplexity int) int
|
||||
}
|
||||
|
||||
CreatePasswordPayload struct {
|
||||
Success func(childComplexity int) int
|
||||
}
|
||||
|
||||
CreatePersonalAPIKeyPayload struct {
|
||||
PersonalAPIKeyEdge func(childComplexity int) int
|
||||
Token func(childComplexity int) int
|
||||
@@ -159,7 +163,7 @@ type ComplexityRoot struct {
|
||||
ID func(childComplexity int) int
|
||||
Permission func(childComplexity int, action string) int
|
||||
PersonalAPIKeys func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
Profiles func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) int
|
||||
Profiles func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) int
|
||||
Sessions func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
@@ -499,7 +503,7 @@ type ConnectorResolver interface {
|
||||
Permission(ctx context.Context, obj *types.Connector, action string) (bool, error)
|
||||
}
|
||||
type IdentityResolver interface {
|
||||
Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error)
|
||||
Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error)
|
||||
Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error)
|
||||
PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error)
|
||||
SsoLoginURL(ctx context.Context, obj *types.Identity) (*string, error)
|
||||
@@ -727,6 +731,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.CreateOrganizationPayload.Organization(childComplexity), true
|
||||
|
||||
case "CreatePasswordPayload.success":
|
||||
if e.complexity.CreatePasswordPayload.Success == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.CreatePasswordPayload.Success(childComplexity), true
|
||||
|
||||
case "CreatePersonalAPIKeyPayload.personalAPIKeyEdge":
|
||||
if e.complexity.CreatePersonalAPIKeyPayload.PersonalAPIKeyEdge == nil {
|
||||
break
|
||||
@@ -877,7 +888,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Identity.Profiles(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ProfileOrderBy)), true
|
||||
return e.complexity.Identity.Profiles(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ProfileOrderBy), args["filter"].(*types.ProfileFilter)), true
|
||||
case "Identity.sessions":
|
||||
if e.complexity.Identity.Sessions == nil {
|
||||
break
|
||||
@@ -2357,6 +2368,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputForgotPasswordInput,
|
||||
ec.unmarshalInputInvitationOrder,
|
||||
ec.unmarshalInputInviteUserInput,
|
||||
ec.unmarshalInputProfileFilter,
|
||||
ec.unmarshalInputProfileOrder,
|
||||
ec.unmarshalInputRegenerateSCIMTokenInput,
|
||||
ec.unmarshalInputRemoveUserInput,
|
||||
@@ -2614,6 +2626,7 @@ type Identity implements Node {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ProfileOrder
|
||||
filter: ProfileFilter
|
||||
): ProfileConnection @goField(forceResolver: true)
|
||||
|
||||
sessions(
|
||||
@@ -2985,6 +2998,11 @@ enum ProfileOrderField
|
||||
)
|
||||
}
|
||||
|
||||
input ProfileFilter {
|
||||
excludeContractEnded: Boolean
|
||||
state: ProfileState
|
||||
}
|
||||
|
||||
input ProfileOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/connect/v1/types.ProfileOrderBy"
|
||||
@@ -3101,7 +3119,6 @@ input SignUpInput {
|
||||
|
||||
input ActivateAccountInput {
|
||||
token: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input ForgotPasswordInput {
|
||||
@@ -3267,6 +3284,10 @@ type ActivateAccountPayload {
|
||||
profile: Profile
|
||||
}
|
||||
|
||||
type CreatePasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload {
|
||||
success: Boolean!
|
||||
}
|
||||
@@ -3557,6 +3578,11 @@ func (ec *executionContext) field_Identity_profiles_args(ctx context.Context, ra
|
||||
return nil, err
|
||||
}
|
||||
args["orderBy"] = arg4
|
||||
arg5, err := graphql.ProcessArgField(ctx, rawArgs, "filter", ec.unmarshalOProfileFilter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfileFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["filter"] = arg5
|
||||
return args, nil
|
||||
}
|
||||
|
||||
@@ -4730,6 +4756,35 @@ func (ec *executionContext) fieldContext_CreateOrganizationPayload_membership(_
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _CreatePasswordPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.CreatePasswordPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_CreatePasswordPayload_success,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Success, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNBoolean2bool,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_CreatePasswordPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "CreatePasswordPayload",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _CreatePersonalAPIKeyPayload_personalAPIKeyEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreatePersonalAPIKeyPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -5377,7 +5432,7 @@ func (ec *executionContext) _Identity_profiles(ctx context.Context, field graphq
|
||||
ec.fieldContext_Identity_profiles,
|
||||
func(ctx context.Context) (any, error) {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
return ec.resolvers.Identity().Profiles(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ProfileOrderBy))
|
||||
return ec.resolvers.Identity().Profiles(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ProfileOrderBy), fc.Args["filter"].(*types.ProfileFilter))
|
||||
},
|
||||
nil,
|
||||
ec.marshalOProfileConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfileConnection,
|
||||
@@ -15129,7 +15184,7 @@ func (ec *executionContext) unmarshalInputActivateAccountInput(ctx context.Conte
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"token", "password"}
|
||||
fieldsInOrder := [...]string{"token"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
@@ -15143,13 +15198,6 @@ func (ec *executionContext) unmarshalInputActivateAccountInput(ctx context.Conte
|
||||
return it, err
|
||||
}
|
||||
it.Token = data
|
||||
case "password":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
|
||||
data, err := ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Password = data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15770,6 +15818,40 @@ func (ec *executionContext) unmarshalInputInviteUserInput(ctx context.Context, o
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputProfileFilter(ctx context.Context, obj any) (types.ProfileFilter, error) {
|
||||
var it types.ProfileFilter
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"excludeContractEnded", "state"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "excludeContractEnded":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("excludeContractEnded"))
|
||||
data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.ExcludeContractEnded = data
|
||||
case "state":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||
data, err := ec.unmarshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.State = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputProfileOrder(ctx context.Context, obj any) (types.ProfileOrderBy, error) {
|
||||
var it types.ProfileOrderBy
|
||||
asMap := map[string]any{}
|
||||
@@ -16955,6 +17037,45 @@ func (ec *executionContext) _CreateOrganizationPayload(ctx context.Context, sel
|
||||
return out
|
||||
}
|
||||
|
||||
var createPasswordPayloadImplementors = []string{"CreatePasswordPayload"}
|
||||
|
||||
func (ec *executionContext) _CreatePasswordPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreatePasswordPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, createPasswordPayloadImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
deferred := make(map[string]*graphql.FieldSet)
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("CreatePasswordPayload")
|
||||
case "success":
|
||||
out.Values[i] = ec._CreatePasswordPayload_success(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch(ctx)
|
||||
if out.Invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
|
||||
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
|
||||
|
||||
for label, dfs := range deferred {
|
||||
ec.processDeferredGroup(graphql.DeferredGroup{
|
||||
Label: label,
|
||||
Path: graphql.GetPath(ctx),
|
||||
FieldSet: dfs,
|
||||
Context: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
var createPersonalAPIKeyPayloadImplementors = []string{"CreatePersonalAPIKeyPayload"}
|
||||
|
||||
func (ec *executionContext) _CreatePersonalAPIKeyPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreatePersonalAPIKeyPayload) graphql.Marshaler {
|
||||
@@ -23298,6 +23419,14 @@ func (ec *executionContext) marshalOProfileConnection2ᚖgoᚗproboᚗincᚋprob
|
||||
return ec._ProfileConnection(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOProfileFilter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfileFilter(ctx context.Context, v any) (*types.ProfileFilter, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
res, err := ec.unmarshalInputProfileFilter(ctx, v)
|
||||
return &res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOProfileOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfileOrderBy(ctx context.Context, v any) (*types.ProfileOrderBy, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
@@ -23306,6 +23435,36 @@ func (ec *executionContext) unmarshalOProfileOrder2ᚖgoᚗproboᚗincᚋprobo
|
||||
return &res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState(ctx context.Context, v any) (*coredata.ProfileState, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState[tmp]
|
||||
return &res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState(ctx context.Context, sel ast.SelectionSet, v *coredata.ProfileState) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
_ = sel
|
||||
_ = ctx
|
||||
res := graphql.MarshalString(marshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState[*v])
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState = map[string]coredata.ProfileState{
|
||||
"ACTIVE": coredata.ProfileStateActive,
|
||||
"INACTIVE": coredata.ProfileStateInactive,
|
||||
}
|
||||
marshalOProfileState2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState = map[coredata.ProfileState]string{
|
||||
coredata.ProfileStateActive: "ACTIVE",
|
||||
coredata.ProfileStateInactive: "INACTIVE",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) marshalORegenerateSCIMTokenPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐRegenerateSCIMTokenPayload(ctx context.Context, sel ast.SelectionSet, v *types.RegenerateSCIMTokenPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
|
||||
@@ -30,6 +30,7 @@ type (
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
Filters *coredata.MembershipProfileFilter
|
||||
}
|
||||
)
|
||||
|
||||
@@ -37,6 +38,7 @@ func NewProfileConnection(
|
||||
p *page.Page[*coredata.MembershipProfile, coredata.MembershipProfileOrderField],
|
||||
resolver any,
|
||||
parentID gid.GID,
|
||||
filters *coredata.MembershipProfileFilter,
|
||||
) *ProfileConnection {
|
||||
edges := make([]*ProfileEdge, len(p.Data))
|
||||
for i, profile := range p.Data {
|
||||
@@ -49,6 +51,7 @@ func NewProfileConnection(
|
||||
|
||||
Resolver: resolver,
|
||||
ParentID: parentID,
|
||||
Filters: filters,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ type AcceptInvitationPayload struct {
|
||||
}
|
||||
|
||||
type ActivateAccountInput struct {
|
||||
Token string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type ActivateAccountPayload struct {
|
||||
@@ -92,6 +91,10 @@ type CreateOrganizationPayload struct {
|
||||
Membership *Membership `json:"membership"`
|
||||
}
|
||||
|
||||
type CreatePasswordPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type CreatePersonalAPIKeyInput struct {
|
||||
Name string `json:"name"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
@@ -339,6 +342,11 @@ type ProfileEdge struct {
|
||||
Node *Profile `json:"node"`
|
||||
}
|
||||
|
||||
type ProfileFilter struct {
|
||||
ExcludeContractEnded *bool `json:"excludeContractEnded,omitempty"`
|
||||
State *coredata.ProfileState `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
|
||||
|
||||
@@ -33,17 +33,24 @@ func (r *connectorResolver) Permission(ctx context.Context, obj *types.Connector
|
||||
}
|
||||
|
||||
// Profiles is the resolver for the profiles field.
|
||||
func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) {
|
||||
func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) {
|
||||
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList, authz.WithSkipAssumptionCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := coredata.NewMembershipProfileFilter(nil)
|
||||
filters := coredata.NewMembershipProfileFilter(nil)
|
||||
if filter != nil {
|
||||
filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded)
|
||||
if filter.State != nil {
|
||||
filters.WithState(*filter.State)
|
||||
}
|
||||
}
|
||||
|
||||
if gqlutils.OnlyTotalCountSelected(ctx) {
|
||||
return &types.ProfileConnection{
|
||||
Resolver: r,
|
||||
ParentID: obj.ID,
|
||||
Filters: filters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -60,13 +67,13 @@ func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, fi
|
||||
|
||||
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filter)
|
||||
page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filters)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewProfileConnection(page, r, obj.ID), nil
|
||||
return types.NewProfileConnection(page, r, obj.ID, filters), nil
|
||||
}
|
||||
|
||||
// Sessions is the resolver for the sessions field.
|
||||
@@ -381,7 +388,6 @@ func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.Acti
|
||||
ctx,
|
||||
&iam.CreateIdentityFromInvitationRequest{
|
||||
InvitationToken: input.Token,
|
||||
Password: input.Password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -565,19 +571,19 @@ func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input
|
||||
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue)
|
||||
if err != nil {
|
||||
var (
|
||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
errPasswordRequired *iam.ErrPasswordRequired
|
||||
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired
|
||||
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
||||
)
|
||||
|
||||
switch {
|
||||
case errors.As(err, &errMembershipNotFound):
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
|
||||
case errors.As(err, &errPasswordRequired):
|
||||
case errors.As(err, &errPasswordAuthenticationRequired):
|
||||
return &types.AssumeOrganizationSessionPayload{
|
||||
Result: types.PasswordRequired{
|
||||
Reason: types.ReauthenticationReason(errPasswordRequired.Reason),
|
||||
Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason),
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -1199,7 +1205,7 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewProfileConnection(page, r, obj.ID), nil
|
||||
return types.NewProfileConnection(page, r, obj.ID, filter), nil
|
||||
}
|
||||
|
||||
// SamlConfigurations is the resolver for the samlConfigurations field.
|
||||
|
||||
Reference in New Issue
Block a user