Add an account activation step in the signing request flow when needed
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
22
apps/console/src/hooks/useSafeContinueUrl.ts
Normal file
22
apps/console/src/hooks/useSafeContinueUrl.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useSearchParams } from "react-router";
|
||||||
|
|
||||||
|
export function useSafeContinueUrl(fallbackUrl?: URL): URL {
|
||||||
|
fallbackUrl = new URL(fallbackUrl ?? window.location.origin, window.location.origin);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const continueUrlParam = searchParams.get("continue");
|
||||||
|
let safeContinueUrl: URL;
|
||||||
|
if (continueUrlParam) {
|
||||||
|
let continueUrl: URL;
|
||||||
|
try {
|
||||||
|
continueUrl = new URL(continueUrlParam, window.location.origin);
|
||||||
|
} catch {
|
||||||
|
continueUrl = fallbackUrl;
|
||||||
|
}
|
||||||
|
safeContinueUrl = new URL(continueUrl.pathname + continueUrl.search, window.location.origin);
|
||||||
|
} else {
|
||||||
|
safeContinueUrl = fallbackUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeContinueUrl;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { Link, useNavigate, useSearchParams } from "react-router";
|
|||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { ActivateAccountPageMutation$data, ActivateAccountPageMutation } from "#/__generated__/iam/ActivateAccountPageMutation.graphql";
|
import type { ActivateAccountPageMutation$data, ActivateAccountPageMutation } from "#/__generated__/iam/ActivateAccountPageMutation.graphql";
|
||||||
|
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
|
||||||
|
|
||||||
const activateAccountMutation = graphql`
|
const activateAccountMutation = graphql`
|
||||||
mutation ActivateAccountPageMutation(
|
mutation ActivateAccountPageMutation(
|
||||||
@@ -22,9 +23,10 @@ const activateAccountMutation = graphql`
|
|||||||
export default function ActivateAccountPage() {
|
export default function ActivateAccountPage() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const navigate = useNavigate();
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const submittedRef = useRef<boolean>(false);
|
const submittedRef = useRef<boolean>(false);
|
||||||
|
const safeContinueUrl = useSafeContinueUrl();
|
||||||
|
|
||||||
usePageTitle(__("Activate Account"));
|
usePageTitle(__("Activate Account"));
|
||||||
|
|
||||||
@@ -44,6 +46,7 @@ export default function ActivateAccountPage() {
|
|||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// FIXME: If already activated redirect too
|
||||||
}
|
}
|
||||||
toast({
|
toast({
|
||||||
title: __("Activation failed"),
|
title: __("Activation failed"),
|
||||||
@@ -69,12 +72,22 @@ export default function ActivateAccountPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activateAccount.createPasswordToken) {
|
if (activateAccount.createPasswordToken) {
|
||||||
|
const search = new URLSearchParams([
|
||||||
|
["token", activateAccount.createPasswordToken],
|
||||||
|
["continue", safeContinueUrl.toString()],
|
||||||
|
]);
|
||||||
void navigate(
|
void navigate(
|
||||||
{ pathname: "/auth/create-password", search: `?token=${activateAccount.createPasswordToken}` },
|
{
|
||||||
|
pathname: "/auth/create-password",
|
||||||
|
search: "?" + search.toString(),
|
||||||
|
},
|
||||||
{ replace: true },
|
{ replace: true },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
void navigate("/", { replace: true });
|
void navigate({
|
||||||
|
pathname: safeContinueUrl.pathname,
|
||||||
|
search: safeContinueUrl.search,
|
||||||
|
}, { replace: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
@@ -85,7 +98,7 @@ export default function ActivateAccountPage() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [__, toast, activateAccount, navigate]);
|
}, [__, toast, activateAccount, navigate, safeContinueUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = searchParams.get("token");
|
const token = searchParams.get("token");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { usePageTitle } from "@probo/hooks";
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Button, Field, useToast } from "@probo/ui";
|
import { Button, Field, useToast } from "@probo/ui";
|
||||||
import { useMutation } from "react-relay";
|
import { useMutation } from "react-relay";
|
||||||
import { Link, useNavigate, useSearchParams } from "react-router";
|
import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ export default function CreatePasswordPage() {
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
usePageTitle(__("Create Password"));
|
usePageTitle(__("Create Password"));
|
||||||
|
|
||||||
@@ -61,7 +62,12 @@ export default function CreatePasswordPage() {
|
|||||||
description: __("Account created successfully"),
|
description: __("Account created successfully"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
});
|
});
|
||||||
void navigate("/auth/login", { replace: true });
|
void navigate({
|
||||||
|
pathname: "/auth/login",
|
||||||
|
search: location.search,
|
||||||
|
}, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import { Button, Field, IconChevronLeft, useToast } from "@probo/ui";
|
import { Button, Field, IconChevronLeft, useToast } from "@probo/ui";
|
||||||
import type { FormEventHandler } from "react";
|
import type { FormEventHandler } from "react";
|
||||||
import { useMutation } from "react-relay";
|
import { useMutation } from "react-relay";
|
||||||
import { Link, matchPath, useLocation, useSearchParams } from "react-router";
|
import { Link, matchPath, useLocation } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { PasswordSignInPageMutation } from "#/__generated__/iam/PasswordSignInPageMutation.graphql";
|
import type { PasswordSignInPageMutation } from "#/__generated__/iam/PasswordSignInPageMutation.graphql";
|
||||||
|
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
|
||||||
|
|
||||||
const signInMutation = graphql`
|
const signInMutation = graphql`
|
||||||
mutation PasswordSignInPageMutation($input: SignInInput!) {
|
mutation PasswordSignInPageMutation($input: SignInInput!) {
|
||||||
@@ -20,7 +21,7 @@ const signInMutation = graphql`
|
|||||||
|
|
||||||
export default function PasswordSignInPage() {
|
export default function PasswordSignInPage() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [searchParams] = useSearchParams();
|
const safeContinueUrl = useSafeContinueUrl();
|
||||||
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -36,20 +37,6 @@ export default function PasswordSignInPage() {
|
|||||||
|
|
||||||
if (!emailValue || !passwordValue) return;
|
if (!emailValue || !passwordValue) return;
|
||||||
|
|
||||||
const continueUrlParam = searchParams.get("continue");
|
|
||||||
let safeContinueUrl: URL;
|
|
||||||
if (continueUrlParam) {
|
|
||||||
let continueUrl: URL;
|
|
||||||
try {
|
|
||||||
continueUrl = new URL(continueUrlParam, window.location.origin);
|
|
||||||
} catch {
|
|
||||||
continueUrl = new URL(window.location.origin);
|
|
||||||
}
|
|
||||||
safeContinueUrl = new URL(continueUrl.pathname + continueUrl.search, window.location.origin);
|
|
||||||
} else {
|
|
||||||
safeContinueUrl = new URL(window.location.origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = matchPath(
|
const match = matchPath(
|
||||||
{ path: "/organizations/:organizationId", caseSensitive: false, end: false },
|
{ path: "/organizations/:organizationId", caseSensitive: false, end: false },
|
||||||
safeContinueUrl.pathname,
|
safeContinueUrl.pathname,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { graphql } from "relay-runtime";
|
|||||||
import type { AssumePageMutation } from "#/__generated__/iam/AssumePageMutation.graphql";
|
import type { AssumePageMutation } from "#/__generated__/iam/AssumePageMutation.graphql";
|
||||||
import type { AssumePageQuery } from "#/__generated__/iam/AssumePageQuery.graphql";
|
import type { AssumePageQuery } from "#/__generated__/iam/AssumePageQuery.graphql";
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
|
||||||
|
|
||||||
import AuthLayout from "../auth/AuthLayout";
|
import AuthLayout from "../auth/AuthLayout";
|
||||||
|
|
||||||
@@ -48,28 +49,23 @@ export function AssumePage(props: { queryRef: PreloadedQuery<AssumePageQuery> })
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const safeContinueUrl = useSafeContinueUrl(
|
||||||
|
new URL(window.location.origin + `/organizations/${organizationId}`),
|
||||||
|
);
|
||||||
|
|
||||||
const { viewer } = usePreloadedQuery<AssumePageQuery>(assumePageQuery, queryRef);
|
const { viewer } = usePreloadedQuery<AssumePageQuery>(assumePageQuery, queryRef);
|
||||||
const [assumeOrganizationSession] = useMutation<AssumePageMutation>(assumeMutation);
|
const [assumeOrganizationSession] = useMutation<AssumePageMutation>(assumeMutation);
|
||||||
|
|
||||||
const continueUrlParam = searchParams.get("continue");
|
|
||||||
let safeContinueUrl: string;
|
|
||||||
if (continueUrlParam) {
|
|
||||||
const continueUrl = new URL(continueUrlParam);
|
|
||||||
safeContinueUrl = window.location.origin + continueUrl.pathname + continueUrl.search;
|
|
||||||
} else {
|
|
||||||
safeContinueUrl = window.location.origin + `/organizations/${organizationId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
assumeOrganizationSession({
|
assumeOrganizationSession({
|
||||||
variables: {
|
variables: {
|
||||||
input: { organizationId, continue: safeContinueUrl },
|
input: { organizationId, continue: safeContinueUrl.toString() },
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
if (error instanceof UnAuthenticatedError) {
|
if (error instanceof UnAuthenticatedError) {
|
||||||
const search = new URLSearchParams([
|
const search = new URLSearchParams([
|
||||||
["organization-id", organizationId],
|
["organization-id", organizationId],
|
||||||
["continue", safeContinueUrl],
|
["continue", safeContinueUrl.toString()],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
void navigate({ pathname: "/auth/login", search: "?" + search.toString() });
|
void navigate({ pathname: "/auth/login", search: "?" + search.toString() });
|
||||||
@@ -88,7 +84,7 @@ export function AssumePage(props: { queryRef: PreloadedQuery<AssumePageQuery> })
|
|||||||
switch (result.__typename) {
|
switch (result.__typename) {
|
||||||
case "PasswordRequired":
|
case "PasswordRequired":
|
||||||
search.set("organization-id", organizationId);
|
search.set("organization-id", organizationId);
|
||||||
search.set("continue", safeContinueUrl);
|
search.set("continue", safeContinueUrl.toString());
|
||||||
|
|
||||||
void navigate({ pathname: "/auth/password-login", search: "?" + search.toString() });
|
void navigate({ pathname: "/auth/password-login", search: "?" + search.toString() });
|
||||||
break;
|
break;
|
||||||
@@ -102,7 +98,7 @@ export function AssumePage(props: { queryRef: PreloadedQuery<AssumePageQuery> })
|
|||||||
window.location.href = samlSSOLoginURL.toString();
|
window.location.href = samlSSOLoginURL.toString();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
window.location.href = safeContinueUrl;
|
window.location.href = safeContinueUrl.toString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
htmltemplate "html/template"
|
htmltemplate "html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"mime"
|
"mime"
|
||||||
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
texttemplate "text/template"
|
texttemplate "text/template"
|
||||||
"time"
|
"time"
|
||||||
@@ -338,14 +339,20 @@ func (p *Presenter) RenderInvitation(ctx context.Context, invitationURLPath stri
|
|||||||
return fmt.Sprintf(subjectInvitation, organizationName), textBody, htmlBody, err
|
return fmt.Sprintf(subjectInvitation, organizationName), textBody, htmlBody, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Presenter) RenderDocumentSigning(ctx context.Context, signinURLPath string, organizationName string) (subject string, textBody string, htmlBody *string, err error) {
|
func (p *Presenter) RenderDocumentSigning(
|
||||||
|
ctx context.Context,
|
||||||
|
signingURLPath string,
|
||||||
|
signingURLQuery url.Values,
|
||||||
|
organizationName string,
|
||||||
|
) (subject string, textBody string, htmlBody *string, err error) {
|
||||||
vars, err := p.getCommonVariables(ctx)
|
vars, err := p.getCommonVariables(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
|
return "", "", nil, fmt.Errorf("cannot get common variables: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signingURL := baseurl.MustParse(vars.BaseURL).
|
signingURL := baseurl.MustParse(vars.BaseURL).
|
||||||
AppendPath(signinURLPath).
|
AppendPath(signingURLPath).
|
||||||
|
WithQueryValues(signingURLQuery).
|
||||||
MustString()
|
MustString()
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,20 +17,25 @@ import (
|
|||||||
"go.gearno.de/crypto/uuid"
|
"go.gearno.de/crypto/uuid"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/packages/emails"
|
"go.probo.inc/probo/packages/emails"
|
||||||
|
"go.probo.inc/probo/pkg/baseurl"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/docgen"
|
"go.probo.inc/probo/pkg/docgen"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/html2pdf"
|
"go.probo.inc/probo/pkg/html2pdf"
|
||||||
|
"go.probo.inc/probo/pkg/iam"
|
||||||
"go.probo.inc/probo/pkg/mail"
|
"go.probo.inc/probo/pkg/mail"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.probo.inc/probo/pkg/validator"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.probo.inc/probo/pkg/watermarkpdf"
|
"go.probo.inc/probo/pkg/watermarkpdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
DocumentService struct {
|
DocumentService struct {
|
||||||
svc *TenantService
|
svc *TenantService
|
||||||
html2pdfConverter *html2pdf.Converter
|
html2pdfConverter *html2pdf.Converter
|
||||||
|
invitationTokenValidity time.Duration
|
||||||
|
tokenSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrSignatureNotCancellable struct {
|
ErrSignatureNotCancellable struct {
|
||||||
@@ -588,6 +594,8 @@ func (s *DocumentService) SendSigningNotifications(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
@@ -604,9 +612,46 @@ func (s *DocumentService) SendSigningNotifications(
|
|||||||
for _, signatory := range signatories {
|
for _, signatory := range signatories {
|
||||||
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, signatory.FullName)
|
emailPresenter := emails.NewPresenter(s.svc.fileManager, s.svc.bucket, s.svc.baseURL, signatory.FullName)
|
||||||
|
|
||||||
|
var (
|
||||||
|
employeeDocumentsURLPath = "/organizations/" + organizationID.String() + "/employee"
|
||||||
|
emailLinkURLPath = employeeDocumentsURLPath
|
||||||
|
query url.Values
|
||||||
|
)
|
||||||
|
if signatory.State != coredata.ProfileStateActive {
|
||||||
|
if signatory.Source != coredata.ProfileSourceSCIM {
|
||||||
|
invitation := &coredata.Invitation{
|
||||||
|
ID: gid.New(organizationID.TenantID(), coredata.InvitationEntityType),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
UserID: signatory.ID,
|
||||||
|
Status: coredata.InvitationStatusPending,
|
||||||
|
ExpiresAt: now.Add(s.invitationTokenValidity),
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
if err := invitation.Insert(ctx, tx, coredata.NewScopeFromObjectID(organizationID)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert invitation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
invitationToken, err := statelesstoken.NewToken(
|
||||||
|
s.tokenSecret,
|
||||||
|
iam.TokenTypeOrganizationInvitation,
|
||||||
|
s.invitationTokenValidity,
|
||||||
|
iam.InvitationTokenData{InvitationID: invitation.ID},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot generate invitation token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailLinkURLPath = "/auth/activate-account"
|
||||||
|
continueURL := baseurl.MustParse(s.svc.baseURL).AppendPath(employeeDocumentsURLPath).MustString()
|
||||||
|
query.Add("token", invitationToken)
|
||||||
|
query.Add("continue", continueURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
subject, textBody, htmlBody, err := emailPresenter.RenderDocumentSigning(
|
subject, textBody, htmlBody, err := emailPresenter.RenderDocumentSigning(
|
||||||
ctx,
|
ctx,
|
||||||
"/organizations/"+organizationID.String()+"/employee",
|
emailLinkURLPath,
|
||||||
|
query,
|
||||||
organization.Name,
|
organization.Name,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -49,19 +49,20 @@ type ExportService interface {
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Service struct {
|
Service struct {
|
||||||
pg *pg.Client
|
pg *pg.Client
|
||||||
s3 *s3.Client
|
s3 *s3.Client
|
||||||
bucket string
|
bucket string
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
baseURL string
|
baseURL string
|
||||||
tokenSecret string
|
tokenSecret string
|
||||||
agentConfig agents.Config
|
agentConfig agents.Config
|
||||||
html2pdfConverter *html2pdf.Converter
|
html2pdfConverter *html2pdf.Converter
|
||||||
acmeService *certmanager.ACMEService
|
acmeService *certmanager.ACMEService
|
||||||
fileManager *filemanager.Service
|
fileManager *filemanager.Service
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
slack *slack.Service
|
slack *slack.Service
|
||||||
esign *esign.Service
|
esign *esign.Service
|
||||||
|
invitationTokenValidity time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
TenantService struct {
|
TenantService struct {
|
||||||
@@ -133,6 +134,7 @@ func NewService(
|
|||||||
slackService *slack.Service,
|
slackService *slack.Service,
|
||||||
iamService *iam.Service,
|
iamService *iam.Service,
|
||||||
esignService *esign.Service,
|
esignService *esign.Service,
|
||||||
|
invitationTokenValidity time.Duration,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
return nil, fmt.Errorf("bucket is required")
|
return nil, fmt.Errorf("bucket is required")
|
||||||
@@ -141,19 +143,20 @@ func NewService(
|
|||||||
iamService.Authorizer.RegisterPolicySet(ProboPolicySet())
|
iamService.Authorizer.RegisterPolicySet(ProboPolicySet())
|
||||||
|
|
||||||
svc := &Service{
|
svc := &Service{
|
||||||
pg: pgClient,
|
pg: pgClient,
|
||||||
s3: s3Client,
|
s3: s3Client,
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
tokenSecret: tokenSecret,
|
tokenSecret: tokenSecret,
|
||||||
agentConfig: agentConfig,
|
agentConfig: agentConfig,
|
||||||
html2pdfConverter: html2pdfConverter,
|
html2pdfConverter: html2pdfConverter,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
fileManager: fileManagerService,
|
fileManager: fileManagerService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
slack: slackService,
|
slack: slackService,
|
||||||
esign: esignService,
|
esign: esignService,
|
||||||
|
invitationTokenValidity: invitationTokenValidity,
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
@@ -195,8 +198,10 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
|||||||
}
|
}
|
||||||
tenantService.Vendors = &VendorService{svc: tenantService}
|
tenantService.Vendors = &VendorService{svc: tenantService}
|
||||||
tenantService.Documents = &DocumentService{
|
tenantService.Documents = &DocumentService{
|
||||||
svc: tenantService,
|
svc: tenantService,
|
||||||
html2pdfConverter: s.html2pdfConverter,
|
html2pdfConverter: s.html2pdfConverter,
|
||||||
|
invitationTokenValidity: s.invitationTokenValidity,
|
||||||
|
tokenSecret: s.tokenSecret,
|
||||||
}
|
}
|
||||||
tenantService.Organizations = &OrganizationService{
|
tenantService.Organizations = &OrganizationService{
|
||||||
svc: tenantService,
|
svc: tenantService,
|
||||||
|
|||||||
@@ -451,6 +451,7 @@ func (impl *Implm) Run(
|
|||||||
slackService,
|
slackService,
|
||||||
iamService,
|
iamService,
|
||||||
esignService,
|
esignService,
|
||||||
|
time.Duration(impl.cfg.Auth.InvitationConfirmationTokenValidity)*time.Second,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create probo service: %w", err)
|
return fmt.Errorf("cannot create probo service: %w", err)
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload,
|
|||||||
return &types.SignOutPayload{Success: true}, nil
|
return &types.SignOutPayload{Success: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivateAccount is the resolver for the signUpFromInvitation field.
|
// ActivateAccount is the resolver for the activateAccount field.
|
||||||
func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) {
|
func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) {
|
||||||
session := authn.SessionFromContext(ctx)
|
session := authn.SessionFromContext(ctx)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user