Implement continue on verify magic link
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -8,13 +8,14 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
usePreloadedQuery,
|
usePreloadedQuery,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
|
import { useSearchParams } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||||
import { getPathPrefix } from "#/utils/pathPrefix";
|
import { getPathPrefix } from "#/utils/pathPrefix";
|
||||||
|
|
||||||
import type { ConnectPageMutation } from "./__generated__/ConnectPageMutation.graphql";
|
import type { ConnectPageMutation, SendMagicLinkInput } from "./__generated__/ConnectPageMutation.graphql";
|
||||||
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
||||||
|
|
||||||
export const connectPageQuery = graphql`
|
export const connectPageQuery = graphql`
|
||||||
@@ -53,11 +54,22 @@ export function ConnectPage(props: {
|
|||||||
const [magicLinkSent, setMagicLinkSent] = useState<boolean>(false);
|
const [magicLinkSent, setMagicLinkSent] = useState<boolean>(false);
|
||||||
const interval = useRef<NodeJS.Timeout>(undefined);
|
const interval = useRef<NodeJS.Timeout>(undefined);
|
||||||
const [timer, setTimer] = useState<number>(timerDurationSeconds);
|
const [timer, setTimer] = useState<number>(timerDurationSeconds);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentTrustCenter: { organization },
|
currentTrustCenter: { organization },
|
||||||
} = usePreloadedQuery<ConnectPageQuery>(connectPageQuery, queryRef);
|
} = usePreloadedQuery<ConnectPageQuery>(connectPageQuery, queryRef);
|
||||||
|
|
||||||
|
const continueUrlParam = searchParams.get("continue");
|
||||||
|
let safeContinueUrl: string;
|
||||||
|
if (continueUrlParam) {
|
||||||
|
const continueUrl = new URL(continueUrlParam);
|
||||||
|
safeContinueUrl = window.location.origin + continueUrl.pathname + continueUrl.search;
|
||||||
|
} else {
|
||||||
|
const pathPrefix = getPathPrefix();
|
||||||
|
safeContinueUrl = window.location.origin + pathPrefix ? getPathPrefix() : "/";
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!magicLinkSent && interval.current) {
|
if (!magicLinkSent && interval.current) {
|
||||||
clearInterval(interval.current);
|
clearInterval(interval.current);
|
||||||
@@ -92,14 +104,18 @@ export function ConnectPage(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = handleSubmitWrapper(({ email }: FormData) => {
|
const handleSubmit = handleSubmitWrapper(({ email }: FormData) => {
|
||||||
|
const input: SendMagicLinkInput = { email };
|
||||||
|
if (safeContinueUrl) {
|
||||||
|
input.continue = safeContinueUrl;
|
||||||
|
}
|
||||||
sendMagicLink({
|
sendMagicLink({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
email,
|
email,
|
||||||
|
continue: safeContinueUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onCompleted: (data, errors: GraphQLError[] | null) => {
|
onCompleted: (_, errors: GraphQLError[] | null) => {
|
||||||
console.log(data, errors);
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
for (const err of errors) {
|
for (const err of errors) {
|
||||||
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type { VerifyMagicLinkPageMutation } from "./__generated__/VerifyMagicLin
|
|||||||
const verifyMagicLinkMutation = graphql`
|
const verifyMagicLinkMutation = graphql`
|
||||||
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
|
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
|
||||||
verifyMagicLink(input: $input) {
|
verifyMagicLink(input: $input) {
|
||||||
success
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -38,7 +38,7 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
variables: {
|
variables: {
|
||||||
input: { token },
|
input: { token },
|
||||||
},
|
},
|
||||||
onCompleted: (_, errors: GraphQLError[] | null) => {
|
onCompleted: (response, errors: GraphQLError[] | null) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
for (const err of errors) {
|
for (const err of errors) {
|
||||||
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
|
||||||
@@ -55,13 +55,21 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { verifyMagicLink } = response;
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: __("Success"),
|
title: __("Success"),
|
||||||
description: __("Your have successfully signed in"),
|
description: __("Your have successfully signed in"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
});
|
});
|
||||||
const pathPrefix = getPathPrefix();
|
|
||||||
window.location.href = pathPrefix ? getPathPrefix() : "/";
|
if (verifyMagicLink?.continue) {
|
||||||
|
const continueUrl = new URL(verifyMagicLink.continue);
|
||||||
|
window.location.href = window.location.origin + continueUrl.pathname + continueUrl.search;
|
||||||
|
} else {
|
||||||
|
const pathPrefix = getPathPrefix();
|
||||||
|
window.location.href = pathPrefix ? getPathPrefix() : "/";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<bcbab3713eaca3141e6dfb8f54a54e7d>>
|
* @generated SignedSource<<711ecaa392c23004a3bd1dfb24a5751f>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type SendMagicLinkInput = {
|
export type SendMagicLinkInput = {
|
||||||
|
continue?: string | null | undefined;
|
||||||
email: any;
|
email: any;
|
||||||
};
|
};
|
||||||
export type ConnectPageMutation$variables = {
|
export type ConnectPageMutation$variables = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<5037d9722f3c6cc15c2a323ef954e3d6>>
|
* @generated SignedSource<<5570f368d6cc3c3be80a32390e12c8f9>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +17,7 @@ export type VerifyMagicLinkPageMutation$variables = {
|
|||||||
};
|
};
|
||||||
export type VerifyMagicLinkPageMutation$data = {
|
export type VerifyMagicLinkPageMutation$data = {
|
||||||
readonly verifyMagicLink: {
|
readonly verifyMagicLink: {
|
||||||
readonly success: boolean;
|
readonly continue: string | null | undefined;
|
||||||
} | null | undefined;
|
} | null | undefined;
|
||||||
};
|
};
|
||||||
export type VerifyMagicLinkPageMutation = {
|
export type VerifyMagicLinkPageMutation = {
|
||||||
@@ -52,7 +52,7 @@ v1 = [
|
|||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "success",
|
"name": "continue",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -77,16 +77,16 @@ return {
|
|||||||
"selections": (v1/*: any*/)
|
"selections": (v1/*: any*/)
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "07cf89de3f37725d847cda46557467f5",
|
"cacheID": "05d0e504b6f11ad7dd11ed84059a96ac",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "VerifyMagicLinkPageMutation",
|
"name": "VerifyMagicLinkPageMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation VerifyMagicLinkPageMutation(\n $input: VerifyMagicLinkInput!\n) {\n verifyMagicLink(input: $input) {\n success\n }\n}\n"
|
"text": "mutation VerifyMagicLinkPageMutation(\n $input: VerifyMagicLinkInput!\n) {\n verifyMagicLink(input: $input) {\n continue\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "074415601c4d50f50d177c06dfab64ef";
|
(node as any).hash = "cc9e7f7886d9d61b95f13c5ba66c41ec";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ type (
|
|||||||
Email mail.Addr
|
Email mail.Addr
|
||||||
URLPath string
|
URLPath string
|
||||||
OrganizationID gid.GID
|
OrganizationID gid.GID
|
||||||
|
Continue *string
|
||||||
// If users tries to connect to compliance page, we must brand the emails accordingly
|
// If users tries to connect to compliance page, we must brand the emails accordingly
|
||||||
CompliancePageID *gid.GID
|
CompliancePageID *gid.GID
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
MagicLinkData struct {
|
MagicLinkData struct {
|
||||||
Email mail.Addr `json:"email"`
|
Email mail.Addr `json:"email"`
|
||||||
|
Continue *string `json:"continue"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -593,7 +595,8 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
|
|||||||
TokenTypeMagicLink,
|
TokenTypeMagicLink,
|
||||||
s.magicLinkTokenValidity,
|
s.magicLinkTokenValidity,
|
||||||
MagicLinkData{
|
MagicLinkData{
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
|
Continue: req.Continue,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -670,16 +673,16 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString string) (*coredata.Identity, *coredata.Session, error) {
|
func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString string) (*coredata.Identity, *coredata.Session, *string, error) {
|
||||||
var (
|
var (
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
identity = &coredata.Identity{}
|
|
||||||
session = &coredata.Session{}
|
session = &coredata.Session{}
|
||||||
|
identity = &coredata.Identity{}
|
||||||
)
|
)
|
||||||
|
|
||||||
payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString)
|
payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, NewInvalidTokenError()
|
return nil, nil, nil, NewInvalidTokenError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.pg.WithTx(
|
if err := s.pg.WithTx(
|
||||||
@@ -737,10 +740,10 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString s
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity, session, nil
|
return identity, session, payload.Data.Continue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthService) UpdateIdentity(ctx context.Context, identityID gid.GID, fullName string) (*coredata.Identity, error) {
|
func (s *AuthService) UpdateIdentity(ctx context.Context, identityID gid.GID, fullName string) (*coredata.Identity, error) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/baseurl"
|
"go.probo.inc/probo/pkg/baseurl"
|
||||||
"go.probo.inc/probo/pkg/esign"
|
"go.probo.inc/probo/pkg/esign"
|
||||||
"go.probo.inc/probo/pkg/iam"
|
"go.probo.inc/probo/pkg/iam"
|
||||||
|
"go.probo.inc/probo/pkg/saferedirect"
|
||||||
"go.probo.inc/probo/pkg/securecookie"
|
"go.probo.inc/probo/pkg/securecookie"
|
||||||
"go.probo.inc/probo/pkg/server/api/authn"
|
"go.probo.inc/probo/pkg/server/api/authn"
|
||||||
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
|
||||||
@@ -38,6 +39,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *e
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
sessionCookie: authn.NewCookie(&cookieConfig),
|
sessionCookie: authn.NewCookie(&cookieConfig),
|
||||||
|
safeRedirect: &saferedirect.SafeRedirect{AllowedHost: baseURL.Host()},
|
||||||
},
|
},
|
||||||
Directives: schema.DirectiveRoot{
|
Directives: schema.DirectiveRoot{
|
||||||
Session: session.Directive,
|
Session: session.Directive,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/esign"
|
"go.probo.inc/probo/pkg/esign"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/iam"
|
"go.probo.inc/probo/pkg/iam"
|
||||||
|
"go.probo.inc/probo/pkg/saferedirect"
|
||||||
"go.probo.inc/probo/pkg/securecookie"
|
"go.probo.inc/probo/pkg/securecookie"
|
||||||
"go.probo.inc/probo/pkg/server/api/authn"
|
"go.probo.inc/probo/pkg/server/api/authn"
|
||||||
"go.probo.inc/probo/pkg/server/api/compliancepage"
|
"go.probo.inc/probo/pkg/server/api/compliancepage"
|
||||||
@@ -52,6 +53,7 @@ type (
|
|||||||
iam *iam.Service
|
iam *iam.Service
|
||||||
sessionCookie *authn.Cookie
|
sessionCookie *authn.Cookie
|
||||||
baseURL *baseurl.BaseURL
|
baseURL *baseurl.BaseURL
|
||||||
|
safeRedirect *saferedirect.SafeRedirect
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -553,6 +553,7 @@ type TrustCenterAccess implements Node {
|
|||||||
|
|
||||||
input SendMagicLinkInput {
|
input SendMagicLinkInput {
|
||||||
email: EmailAddr!
|
email: EmailAddr!
|
||||||
|
continue: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMagicLinkPayload {
|
type SendMagicLinkPayload {
|
||||||
@@ -564,7 +565,7 @@ input VerifyMagicLinkInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VerifyMagicLinkPayload {
|
type VerifyMagicLinkPayload {
|
||||||
success: Boolean!
|
continue: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestAccessesPayload {
|
type RequestAccessesPayload {
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VerifyMagicLinkPayload struct {
|
VerifyMagicLinkPayload struct {
|
||||||
Success func(childComplexity int) int
|
Continue func(childComplexity int) int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,12 +1205,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
|
|
||||||
return e.ComplexityRoot.VendorEdge.Node(childComplexity), true
|
return e.ComplexityRoot.VendorEdge.Node(childComplexity), true
|
||||||
|
|
||||||
case "VerifyMagicLinkPayload.success":
|
case "VerifyMagicLinkPayload.continue":
|
||||||
if e.ComplexityRoot.VerifyMagicLinkPayload.Success == nil {
|
if e.ComplexityRoot.VerifyMagicLinkPayload.Continue == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.ComplexityRoot.VerifyMagicLinkPayload.Success(childComplexity), true
|
return e.ComplexityRoot.VerifyMagicLinkPayload.Continue(childComplexity), true
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
@@ -1860,6 +1860,7 @@ type TrustCenterAccess implements Node {
|
|||||||
|
|
||||||
input SendMagicLinkInput {
|
input SendMagicLinkInput {
|
||||||
email: EmailAddr!
|
email: EmailAddr!
|
||||||
|
continue: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMagicLinkPayload {
|
type SendMagicLinkPayload {
|
||||||
@@ -1871,7 +1872,7 @@ input VerifyMagicLinkInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VerifyMagicLinkPayload {
|
type VerifyMagicLinkPayload {
|
||||||
success: Boolean!
|
continue: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestAccessesPayload {
|
type RequestAccessesPayload {
|
||||||
@@ -3796,8 +3797,8 @@ func (ec *executionContext) fieldContext_Mutation_verifyMagicLink(ctx context.Co
|
|||||||
IsResolver: true,
|
IsResolver: true,
|
||||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "success":
|
case "continue":
|
||||||
return ec.fieldContext_VerifyMagicLinkPayload_success(ctx, field)
|
return ec.fieldContext_VerifyMagicLinkPayload_continue(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type VerifyMagicLinkPayload", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type VerifyMagicLinkPayload", field.Name)
|
||||||
},
|
},
|
||||||
@@ -6855,30 +6856,30 @@ func (ec *executionContext) fieldContext_VendorEdge_node(_ context.Context, fiel
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _VerifyMagicLinkPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.VerifyMagicLinkPayload) (ret graphql.Marshaler) {
|
func (ec *executionContext) _VerifyMagicLinkPayload_continue(ctx context.Context, field graphql.CollectedField, obj *types.VerifyMagicLinkPayload) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
ec.OperationContext,
|
ec.OperationContext,
|
||||||
field,
|
field,
|
||||||
ec.fieldContext_VerifyMagicLinkPayload_success,
|
ec.fieldContext_VerifyMagicLinkPayload_continue,
|
||||||
func(ctx context.Context) (any, error) {
|
func(ctx context.Context) (any, error) {
|
||||||
return obj.Success, nil
|
return obj.Continue, nil
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
ec.marshalNBoolean2bool,
|
ec.marshalOString2ᚖstring,
|
||||||
true,
|
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) fieldContext_VerifyMagicLinkPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
func (ec *executionContext) fieldContext_VerifyMagicLinkPayload_continue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
fc = &graphql.FieldContext{
|
fc = &graphql.FieldContext{
|
||||||
Object: "VerifyMagicLinkPayload",
|
Object: "VerifyMagicLinkPayload",
|
||||||
Field: field,
|
Field: field,
|
||||||
IsMethod: false,
|
IsMethod: false,
|
||||||
IsResolver: false,
|
IsResolver: false,
|
||||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
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 nil, errors.New("field of type String does not have child fields")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return fc, nil
|
return fc, nil
|
||||||
@@ -8559,7 +8560,7 @@ func (ec *executionContext) unmarshalInputSendMagicLinkInput(ctx context.Context
|
|||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email"}
|
fieldsInOrder := [...]string{"email", "continue"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -8573,6 +8574,13 @@ func (ec *executionContext) unmarshalInputSendMagicLinkInput(ctx context.Context
|
|||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
it.Email = data
|
it.Email = data
|
||||||
|
case "continue":
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("continue"))
|
||||||
|
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
|
it.Continue = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return it, nil
|
return it, nil
|
||||||
@@ -11243,11 +11251,8 @@ func (ec *executionContext) _VerifyMagicLinkPayload(ctx context.Context, sel ast
|
|||||||
switch field.Name {
|
switch field.Name {
|
||||||
case "__typename":
|
case "__typename":
|
||||||
out.Values[i] = graphql.MarshalString("VerifyMagicLinkPayload")
|
out.Values[i] = graphql.MarshalString("VerifyMagicLinkPayload")
|
||||||
case "success":
|
case "continue":
|
||||||
out.Values[i] = ec._VerifyMagicLinkPayload_success(ctx, field, obj)
|
out.Values[i] = ec._VerifyMagicLinkPayload_continue(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
|
||||||
out.Invalids++
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,8 @@ type RequestTrustCenterFileAccessInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SendMagicLinkInput struct {
|
type SendMagicLinkInput struct {
|
||||||
Email mail.Addr `json:"email"`
|
Email mail.Addr `json:"email"`
|
||||||
|
Continue *string `json:"continue,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMagicLinkPayload struct {
|
type SendMagicLinkPayload struct {
|
||||||
@@ -296,5 +297,5 @@ type VerifyMagicLinkInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VerifyMagicLinkPayload struct {
|
type VerifyMagicLinkPayload struct {
|
||||||
Success bool `json:"success"`
|
Continue *string `json:"continue,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,11 +157,22 @@ func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framewor
|
|||||||
func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) {
|
func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) {
|
||||||
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
trustCenter := compliancepage.CompliancePageFromContext(ctx)
|
||||||
|
|
||||||
|
var continueURLString *string
|
||||||
|
if input.Continue != nil {
|
||||||
|
safeURL, ok := r.safeRedirect.Validate(*input.Continue)
|
||||||
|
if !ok {
|
||||||
|
return nil, gqlutils.Invalidf(ctx, "invalid continue URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
continueURLString = &safeURL
|
||||||
|
}
|
||||||
|
|
||||||
req := &iam.SendMagicLinkRequest{
|
req := &iam.SendMagicLinkRequest{
|
||||||
Email: input.Email,
|
Email: input.Email,
|
||||||
CompliancePageID: &trustCenter.ID,
|
CompliancePageID: &trustCenter.ID,
|
||||||
OrganizationID: trustCenter.OrganizationID,
|
OrganizationID: trustCenter.OrganizationID,
|
||||||
URLPath: "verify-magic-link",
|
URLPath: "verify-magic-link",
|
||||||
|
Continue: continueURLString,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil {
|
if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil {
|
||||||
@@ -174,7 +185,7 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa
|
|||||||
|
|
||||||
// VerifyMagicLink is the resolver for the verifyMagicLink field.
|
// VerifyMagicLink is the resolver for the verifyMagicLink field.
|
||||||
func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) {
|
func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) {
|
||||||
identity, session, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
|
identity, session, continueURL, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errInvalidToken *iam.ErrInvalidToken
|
var errInvalidToken *iam.ErrInvalidToken
|
||||||
if errors.As(err, &errInvalidToken) {
|
if errors.As(err, &errInvalidToken) {
|
||||||
@@ -196,7 +207,7 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
|
|||||||
r.sessionCookie.Set(w, session)
|
r.sessionCookie.Set(w, session)
|
||||||
|
|
||||||
return &types.VerifyMagicLinkPayload{
|
return &types.VerifyMagicLinkPayload{
|
||||||
Success: true,
|
Continue: continueURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user