satellite/console: configure sending invites to unregistered emails
This change adds a flag to the satellite config indicating whether unregistered email addresses should receive project invitation emails. Change-Id: I0396f25574ddae3f9adaea32a6e7cd15b931bf12
This commit is contained in:
parent
24ae79345b
commit
f8b59a50ff
@ -21,6 +21,7 @@ type Config struct {
|
|||||||
LoginAttemptsWithoutPenalty int `help:"number of times user can try to login without penalty" default:"3"`
|
LoginAttemptsWithoutPenalty int `help:"number of times user can try to login without penalty" default:"3"`
|
||||||
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
FailedLoginPenalty float64 `help:"incremental duration of penalty for failed login attempts in minutes" default:"2.0"`
|
||||||
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
ProjectInvitationExpiration time.Duration `help:"duration that project member invitations are valid for" default:"168h"`
|
||||||
|
UnregisteredInviteEmailsEnabled bool `help:"indicates whether invitation emails can be sent to unregistered email addresses" default:"false"`
|
||||||
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
UserBalanceForUpgrade int64 `help:"amount of base units of US micro dollars needed to upgrade user's tier status" default:"10000000"`
|
||||||
PlacementEdgeURLOverrides PlacementEdgeURLOverrides `help:"placement-specific edge service URL overrides in the format {\"placementID\": {\"authService\": \"...\", \"publicLinksharing\": \"...\", \"internalLinksharing\": \"...\"}, \"placementID2\": ...}"`
|
PlacementEdgeURLOverrides PlacementEdgeURLOverrides `help:"placement-specific edge service URL overrides in the format {\"placementID\": {\"authService\": \"...\", \"publicLinksharing\": \"...\", \"internalLinksharing\": \"...\"}, \"placementID2\": ...}"`
|
||||||
BlockExplorerURL string `help:"url of the transaction block explorer" default:"https://etherscan.io/"`
|
BlockExplorerURL string `help:"url of the transaction block explorer" default:"https://etherscan.io/"`
|
||||||
|
@ -51,6 +51,7 @@ type FrontendConfig struct {
|
|||||||
NeededTransactionConfirmations int `json:"neededTransactionConfirmations"`
|
NeededTransactionConfirmations int `json:"neededTransactionConfirmations"`
|
||||||
ObjectBrowserPaginationEnabled bool `json:"objectBrowserPaginationEnabled"`
|
ObjectBrowserPaginationEnabled bool `json:"objectBrowserPaginationEnabled"`
|
||||||
BillingFeaturesEnabled bool `json:"billingFeaturesEnabled"`
|
BillingFeaturesEnabled bool `json:"billingFeaturesEnabled"`
|
||||||
|
UnregisteredInviteEmailsEnabled bool `json:"unregisteredInviteEmailsEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Satellites is a configuration value that contains a list of satellite names and addresses.
|
// Satellites is a configuration value that contains a list of satellite names and addresses.
|
||||||
|
@ -750,6 +750,7 @@ func (server *Server) frontendConfigHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
NeededTransactionConfirmations: server.neededTokenPaymentConfirmations,
|
NeededTransactionConfirmations: server.neededTokenPaymentConfirmations,
|
||||||
ObjectBrowserPaginationEnabled: server.config.ObjectBrowserPaginationEnabled,
|
ObjectBrowserPaginationEnabled: server.config.ObjectBrowserPaginationEnabled,
|
||||||
BillingFeaturesEnabled: server.config.BillingFeaturesEnabled,
|
BillingFeaturesEnabled: server.config.BillingFeaturesEnabled,
|
||||||
|
UnregisteredInviteEmailsEnabled: server.config.UnregisteredInviteEmailsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.NewEncoder(w).Encode(&cfg)
|
err := json.NewEncoder(w).Encode(&cfg)
|
||||||
|
@ -3821,7 +3821,7 @@ func (s *Service) inviteProjectMembers(ctx context.Context, sender *User, projec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
unverifiedUsers = append(unverifiedUsers, oldest)
|
unverifiedUsers = append(unverifiedUsers, oldest)
|
||||||
} else {
|
} else if s.config.UnregisteredInviteEmailsEnabled {
|
||||||
newUserEmails = append(newUserEmails, email)
|
newUserEmails = append(newUserEmails, email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -400,6 +400,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# url link to terms and conditions page
|
# url link to terms and conditions page
|
||||||
# console.terms-and-conditions-url: https://www.storj.io/terms-of-service/
|
# console.terms-and-conditions-url: https://www.storj.io/terms-of-service/
|
||||||
|
|
||||||
|
# indicates whether invitation emails can be sent to unregistered email addresses
|
||||||
|
# console.unregistered-invite-emails-enabled: false
|
||||||
|
|
||||||
# the default free-tier bandwidth usage limit
|
# the default free-tier bandwidth usage limit
|
||||||
# console.usage-limits.bandwidth.free: 25.00 GB
|
# console.usage-limits.bandwidth.free: 25.00 GB
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
|
|
||||||
import { Validator } from '@/utils/validation';
|
import { Validator } from '@/utils/validation';
|
||||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
@ -71,6 +71,7 @@ import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
|
|||||||
import { useAppStore } from '@/store/modules/appStore';
|
import { useAppStore } from '@/store/modules/appStore';
|
||||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
import { useLoading } from '@/composables/useLoading';
|
import { useLoading } from '@/composables/useLoading';
|
||||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||||
|
|
||||||
@ -86,6 +87,8 @@ const appStore = useAppStore();
|
|||||||
const pmStore = useProjectMembersStore();
|
const pmStore = useProjectMembersStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
|
|
||||||
@ -135,7 +138,18 @@ async function onPrimaryClick(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_MEMBERS_INVITE_SENT);
|
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_MEMBERS_INVITE_SENT);
|
||||||
|
|
||||||
|
if (configStore.state.config.unregisteredInviteEmailsEnabled) {
|
||||||
notify.notify('Invite sent!');
|
notify.notify('Invite sent!');
|
||||||
|
} else {
|
||||||
|
notify.notify(() => [
|
||||||
|
h('p', { class: 'message-title' }, 'Invite sent!'),
|
||||||
|
h('p', { class: 'message-info' }, [
|
||||||
|
'An invitation will be sent to the email address if it belongs to a user on this satellite.',
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
pmStore.setSearchQuery('');
|
pmStore.setSearchQuery('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
import { computed, onMounted, onBeforeUnmount, ref, h } from 'vue';
|
||||||
|
|
||||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||||
@ -168,7 +168,17 @@ async function resendInvites(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await pmStore.reinviteMembers(pmStore.state.selectedProjectMembersEmails, projectsStore.state.selectedProject.id);
|
await pmStore.reinviteMembers(pmStore.state.selectedProjectMembersEmails, projectsStore.state.selectedProject.id);
|
||||||
|
|
||||||
|
if (configStore.state.config.unregisteredInviteEmailsEnabled) {
|
||||||
notify.success('Invites re-sent!');
|
notify.success('Invites re-sent!');
|
||||||
|
} else {
|
||||||
|
notify.success(() => [
|
||||||
|
h('p', { class: 'message-title' }, 'Invites re-sent!'),
|
||||||
|
h('p', { class: 'message-info' }, [
|
||||||
|
'Invitations will be re-sent to the email addresses that belong to users on this satellite.',
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = `Unable to resend project invitations. ${error.message}`;
|
error.message = `Unable to resend project invitations. ${error.message}`;
|
||||||
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, h, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { ProjectMemberItemModel } from '@/types/projectMembers';
|
import { ProjectMemberItemModel } from '@/types/projectMembers';
|
||||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||||
@ -57,6 +57,7 @@ import { useLoading } from '@/composables/useLoading';
|
|||||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||||
import { useAppStore } from '@/store/modules/appStore';
|
import { useAppStore } from '@/store/modules/appStore';
|
||||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||||
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
|
|
||||||
import VLoader from '@/components/common/VLoader.vue';
|
import VLoader from '@/components/common/VLoader.vue';
|
||||||
import HeaderArea from '@/components/team/HeaderArea.vue';
|
import HeaderArea from '@/components/team/HeaderArea.vue';
|
||||||
@ -69,6 +70,7 @@ const analyticsStore = useAnalyticsStore();
|
|||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const pmStore = useProjectMembersStore();
|
const pmStore = useProjectMembersStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
|
const configStore = useConfigStore();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
|
||||||
const { withLoading } = useLoading();
|
const { withLoading } = useLoading();
|
||||||
@ -153,7 +155,18 @@ function onResendClick(member: ProjectMemberItemModel) {
|
|||||||
analyticsStore.eventTriggered(AnalyticsEvent.RESEND_INVITE_CLICKED);
|
analyticsStore.eventTriggered(AnalyticsEvent.RESEND_INVITE_CLICKED);
|
||||||
try {
|
try {
|
||||||
await pmStore.reinviteMembers([member.getEmail()], projectsStore.state.selectedProject.id);
|
await pmStore.reinviteMembers([member.getEmail()], projectsStore.state.selectedProject.id);
|
||||||
notify.notify('Invite resent!');
|
|
||||||
|
if (configStore.state.config.unregisteredInviteEmailsEnabled) {
|
||||||
|
notify.success('Invite re-sent!');
|
||||||
|
} else {
|
||||||
|
notify.success(() => [
|
||||||
|
h('p', { class: 'message-title' }, 'Invites re-sent!'),
|
||||||
|
h('p', { class: 'message-info' }, [
|
||||||
|
'The invitation will be re-sent to the email address if it belongs to a user on this satellite.',
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
pmStore.setSearchQuery('');
|
pmStore.setSearchQuery('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = `Error resending invite. ${error.message}`;
|
error.message = `Error resending invite. ${error.message}`;
|
||||||
|
@ -57,11 +57,12 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|||||||
addNotification(notification);
|
addNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyInfo(message: NotificationMessage): void {
|
function notifyInfo(message: NotificationMessage, title?: string): void {
|
||||||
const notification = new DelayedNotification(
|
const notification = new DelayedNotification(
|
||||||
() => deleteNotification(notification.id),
|
() => deleteNotification(notification.id),
|
||||||
NotificationType.Info,
|
NotificationType.Info,
|
||||||
message,
|
message,
|
||||||
|
title,
|
||||||
);
|
);
|
||||||
|
|
||||||
addNotification(notification);
|
addNotification(notification);
|
||||||
|
@ -13,7 +13,7 @@ export class FrontendConfig {
|
|||||||
satelliteName: string;
|
satelliteName: string;
|
||||||
satelliteNodeURL: string;
|
satelliteNodeURL: string;
|
||||||
stripePublicKey: string;
|
stripePublicKey: string;
|
||||||
partneredSatellites: PartneredSatellite[];
|
partneredSatellites?: PartneredSatellite[];
|
||||||
defaultProjectLimit: number;
|
defaultProjectLimit: number;
|
||||||
generalRequestURL: string;
|
generalRequestURL: string;
|
||||||
projectLimitsIncreaseRequestURL: string;
|
projectLimitsIncreaseRequestURL: string;
|
||||||
@ -48,6 +48,7 @@ export class FrontendConfig {
|
|||||||
neededTransactionConfirmations: number;
|
neededTransactionConfirmations: number;
|
||||||
objectBrowserPaginationEnabled: boolean;
|
objectBrowserPaginationEnabled: boolean;
|
||||||
billingFeaturesEnabled: boolean;
|
billingFeaturesEnabled: boolean;
|
||||||
|
unregisteredInviteEmailsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiCaptchaConfig {
|
export class MultiCaptchaConfig {
|
||||||
@ -55,6 +56,7 @@ export class MultiCaptchaConfig {
|
|||||||
hcaptcha: SingleCaptchaConfig;
|
hcaptcha: SingleCaptchaConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This class was added manually because TypeScript generation is broken.
|
||||||
export class PartneredSatellite {
|
export class PartneredSatellite {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
@ -38,9 +38,9 @@ export class Notificator {
|
|||||||
notificationsStore.notifyError(message, source);
|
notificationsStore.notifyError(message, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public notify(message: NotificationMessage): void {
|
public notify(message: NotificationMessage, title?: string): void {
|
||||||
const notificationsStore = useNotificationsStore();
|
const notificationsStore = useNotificationsStore();
|
||||||
notificationsStore.notifyInfo(message);
|
notificationsStore.notifyInfo(message, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public warning(message: NotificationMessage): void {
|
public warning(message: NotificationMessage): void {
|
||||||
|
@ -157,6 +157,7 @@ import { useNotify } from '@/utils/hooks';
|
|||||||
import { Project } from '@/types/projects';
|
import { Project } from '@/types/projects';
|
||||||
import { SortDirection, tableSizeOptions } from '@/types/common';
|
import { SortDirection, tableSizeOptions } from '@/types/common';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
|
|
||||||
import IconTrash from '@poc/components/icons/IconTrash.vue';
|
import IconTrash from '@poc/components/icons/IconTrash.vue';
|
||||||
import IconCopy from '@poc/components/icons/IconCopy.vue';
|
import IconCopy from '@poc/components/icons/IconCopy.vue';
|
||||||
@ -174,9 +175,9 @@ type RenderedItem = {
|
|||||||
|
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const appStore = useAppStore();
|
|
||||||
const pmStore = useProjectMembersStore();
|
const pmStore = useProjectMembersStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
@ -304,7 +305,14 @@ async function resendInvite(email: string): Promise<void> {
|
|||||||
analyticsStore.eventTriggered(AnalyticsEvent.RESEND_INVITE_CLICKED);
|
analyticsStore.eventTriggered(AnalyticsEvent.RESEND_INVITE_CLICKED);
|
||||||
try {
|
try {
|
||||||
await pmStore.reinviteMembers([email], selectedProject.value.id);
|
await pmStore.reinviteMembers([email], selectedProject.value.id);
|
||||||
notify.notify('Invite resent!');
|
if (configStore.state.config.unregisteredInviteEmailsEnabled) {
|
||||||
|
notify.notify('Invite re-sent!');
|
||||||
|
} else {
|
||||||
|
notify.notify(
|
||||||
|
'The invitation will be re-sent to the email address if it belongs to a user on this satellite.',
|
||||||
|
'Invite re-sent!',
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = `Error resending invite. ${error.message}`;
|
error.message = `Error resending invite. ${error.message}`;
|
||||||
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
|
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_MEMBERS_PAGE);
|
||||||
|
@ -131,6 +131,7 @@ import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
|
|||||||
import { useNotify } from '@/utils/hooks';
|
import { useNotify } from '@/utils/hooks';
|
||||||
import { useLoading } from '@/composables/useLoading';
|
import { useLoading } from '@/composables/useLoading';
|
||||||
import { useUsersStore } from '@/store/modules/usersStore';
|
import { useUsersStore } from '@/store/modules/usersStore';
|
||||||
|
import { useConfigStore } from '@/store/modules/configStore';
|
||||||
|
|
||||||
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
|
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
|
||||||
|
|
||||||
@ -151,6 +152,8 @@ const model = computed<boolean>({
|
|||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const analyticsStore = useAnalyticsStore();
|
const analyticsStore = useAnalyticsStore();
|
||||||
const pmStore = useProjectMembersStore();
|
const pmStore = useProjectMembersStore();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const { isLoading, withLoading } = useLoading();
|
const { isLoading, withLoading } = useLoading();
|
||||||
|
|
||||||
@ -184,7 +187,16 @@ async function onPrimaryClick(): Promise<void> {
|
|||||||
await withLoading(async () => {
|
await withLoading(async () => {
|
||||||
try {
|
try {
|
||||||
await pmStore.inviteMember(email.value, props.projectId);
|
await pmStore.inviteMember(email.value, props.projectId);
|
||||||
|
|
||||||
|
if (configStore.state.config.unregisteredInviteEmailsEnabled) {
|
||||||
notify.success('Invite sent!');
|
notify.success('Invite sent!');
|
||||||
|
} else {
|
||||||
|
notify.success(
|
||||||
|
'An invitation will be sent to the email address if it belongs to a user on this satellite.',
|
||||||
|
'Invite sent!',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
email.value = '';
|
email.value = '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = `Error inviting project member. ${error.message}`;
|
error.message = `Error inviting project member. ${error.message}`;
|
||||||
|
Loading…
Reference in New Issue
Block a user