mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 11:47:56 +01:00
Add private feedback space setting (#392)
This commit is contained in:
committed by
GitHub
parent
2d7f454d0a
commit
0ad1b5eec0
@@ -9,3 +9,15 @@
|
|||||||
color: #fd7e14;
|
color: #fd7e14;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.siteSettingsInfo.siteSettingsInfoSticky {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 16px;
|
||||||
|
z-index: 100;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
span.warning {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ class ApplicationController < ActionController::Base
|
|||||||
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
||||||
|
|
||||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||||
|
before_action :check_tenant_is_private, if: :should_check_tenant_is_private?
|
||||||
|
|
||||||
prepend_before_action :load_tenant_data
|
prepend_before_action :load_tenant_data
|
||||||
|
|
||||||
# Override Devise after sign in path
|
# Override Devise after sign in path
|
||||||
@@ -18,6 +20,15 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Override Devise after sign out path
|
||||||
|
def after_sign_out_path_for(resource_or_scope)
|
||||||
|
if Current.tenant.tenant_setting.is_private
|
||||||
|
new_user_session_path
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def configure_permitted_parameters
|
def configure_permitted_parameters
|
||||||
@@ -69,6 +80,14 @@ class ApplicationController < ActionController::Base
|
|||||||
}, status: :forbidden
|
}, status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_tenant_is_private
|
||||||
|
return unless Current.tenant.tenant_setting.is_private
|
||||||
|
return if user_signed_in?
|
||||||
|
|
||||||
|
flash[:alert] = t('errors.not_logged_in')
|
||||||
|
redirect_to new_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def user_not_authorized
|
def user_not_authorized
|
||||||
@@ -78,4 +97,12 @@ class ApplicationController < ActionController::Base
|
|||||||
error: t('errors.unauthorized')
|
error: t('errors.unauthorized')
|
||||||
}, status: :unauthorized
|
}, status: :unauthorized
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def should_check_tenant_is_private?
|
||||||
|
controller_name == 'posts' ||
|
||||||
|
controller_name == 'boards' ||
|
||||||
|
controller_name == 'comments' ||
|
||||||
|
(controller_name == 'static_pages' && action_name == 'root') ||
|
||||||
|
(controller_name == 'static_pages' && action_name == 'roadmap')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,32 @@ class RegistrationsController < Devise::RegistrationsController
|
|||||||
before_action :set_page_title_new, only: [:new]
|
before_action :set_page_title_new, only: [:new]
|
||||||
before_action :set_page_title_edit, only: [:edit]
|
before_action :set_page_title_edit, only: [:edit]
|
||||||
|
|
||||||
|
# Override create to check whether email registration is possible
|
||||||
|
def create
|
||||||
|
ts = Current.tenant.tenant_setting
|
||||||
|
email = sign_up_params[:email]
|
||||||
|
|
||||||
|
if ts.email_registration_policy == "none_allowed" || (ts.email_registration_policy == "custom_domains_allowed" && !allowed_domain?(email))
|
||||||
|
flash[:alert] = t('errors.email_domain_not_allowed')
|
||||||
|
redirect_to new_user_registration_path and return
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override update to check whether provided email is allowed
|
||||||
|
def update
|
||||||
|
ts = Current.tenant.tenant_setting
|
||||||
|
email = account_update_params[:email]
|
||||||
|
|
||||||
|
if ts.email_registration_policy == "custom_domains_allowed" && !allowed_domain?(email)
|
||||||
|
flash[:alert] = t('errors.email_domain_not_allowed')
|
||||||
|
redirect_to edit_user_registration_path and return
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
# Override destroy to soft delete
|
# Override destroy to soft delete
|
||||||
def destroy
|
def destroy
|
||||||
resource.status = "deleted"
|
resource.status = "deleted"
|
||||||
@@ -28,8 +54,31 @@ class RegistrationsController < Devise::RegistrationsController
|
|||||||
render json: { success: true } # always return true, even if user not found
|
render json: { success: true } # always return true, even if user not found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def after_inactive_sign_up_path_for(resource)
|
||||||
|
if Current.tenant.tenant_setting.is_private
|
||||||
|
# Redirect to log in page, since root page only visible to logged in users
|
||||||
|
new_user_session_path
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def allowed_domain?(email)
|
||||||
|
allowed_email_domains = Current.tenant.tenant_setting.allowed_email_domains
|
||||||
|
|
||||||
|
return false unless email.count('@') == 1
|
||||||
|
return true if allowed_email_domains.blank?
|
||||||
|
|
||||||
|
registering_domain = email.split('@').last
|
||||||
|
allowed_domains = allowed_email_domains.split(',')
|
||||||
|
|
||||||
|
allowed_domains.include?(registering_domain)
|
||||||
|
end
|
||||||
|
|
||||||
def set_page_title_new
|
def set_page_title_new
|
||||||
@page_title = t('common.forms.auth.sign_up')
|
@page_title = t('common.forms.auth.sign_up')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
import I18n from 'i18n-js';
|
import I18n from 'i18n-js';
|
||||||
|
|
||||||
import Box from '../../common/Box';
|
import Box from '../../common/Box';
|
||||||
@@ -6,10 +7,20 @@ import OAuthProvidersList from './OAuthProvidersList';
|
|||||||
import { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
import { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
||||||
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
||||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||||
import ActionLink from '../../common/ActionLink';
|
import { TENANT_SETTING_EMAIL_REGISTRATION_POLICY_ALL_ALLOWED, TENANT_SETTING_EMAIL_REGISTRATION_POLICY_CUSTOM_DOMAINS_ALLOWED, TENANT_SETTING_EMAIL_REGISTRATION_POLICY_NONE_ALLOWED, TenantSettingEmailRegistrationPolicy } from '../../../interfaces/ITenantSetting';
|
||||||
import { LearnMoreIcon } from '../../common/Icons';
|
import { getLabel } from '../../../helpers/formUtils';
|
||||||
|
import HttpStatus from '../../../constants/http_status';
|
||||||
|
import { SmallMutedText } from '../../common/CustomTexts';
|
||||||
|
import Button from '../../common/Button';
|
||||||
|
|
||||||
|
export interface IAuthenticationForm {
|
||||||
|
emailRegistrationPolicy: string;
|
||||||
|
allowedEmailDomains: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
originForm: IAuthenticationForm;
|
||||||
|
|
||||||
oAuths: OAuthsState;
|
oAuths: OAuthsState;
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
submitError: string;
|
submitError: string;
|
||||||
@@ -17,12 +28,14 @@ interface Props {
|
|||||||
handleToggleEnabledOAuth(id: number, enabled: boolean): void;
|
handleToggleEnabledOAuth(id: number, enabled: boolean): void;
|
||||||
handleToggleEnabledDefaultOAuth(id: number, enabled: boolean): void;
|
handleToggleEnabledDefaultOAuth(id: number, enabled: boolean): void;
|
||||||
handleDeleteOAuth(id: number): void;
|
handleDeleteOAuth(id: number): void;
|
||||||
|
handleUpdateTenantSettings(emailRegistrationPolicy: TenantSettingEmailRegistrationPolicy, allowedEmailDomains: string): Promise<any>;
|
||||||
|
|
||||||
setPage: React.Dispatch<React.SetStateAction<AuthenticationPages>>;
|
setPage: React.Dispatch<React.SetStateAction<AuthenticationPages>>;
|
||||||
setSelectedOAuth: React.Dispatch<React.SetStateAction<number>>;
|
setSelectedOAuth: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticationIndexPage = ({
|
const AuthenticationIndexPage = ({
|
||||||
|
originForm,
|
||||||
oAuths,
|
oAuths,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
submitError,
|
submitError,
|
||||||
@@ -30,38 +43,115 @@ const AuthenticationIndexPage = ({
|
|||||||
handleToggleEnabledOAuth,
|
handleToggleEnabledOAuth,
|
||||||
handleToggleEnabledDefaultOAuth,
|
handleToggleEnabledDefaultOAuth,
|
||||||
handleDeleteOAuth,
|
handleDeleteOAuth,
|
||||||
|
handleUpdateTenantSettings,
|
||||||
|
|
||||||
setPage,
|
setPage,
|
||||||
setSelectedOAuth,
|
setSelectedOAuth,
|
||||||
}: Props) => (
|
}: Props) => {
|
||||||
<>
|
const {
|
||||||
<Box customClass="authenticationIndexPage">
|
register,
|
||||||
<h2>{ I18n.t('site_settings.authentication.title') }</h2>
|
handleSubmit,
|
||||||
|
formState: { isDirty, isSubmitSuccessful, errors },
|
||||||
|
setValue,
|
||||||
|
} = useForm<IAuthenticationForm>({
|
||||||
|
defaultValues: {
|
||||||
|
emailRegistrationPolicy: originForm.emailRegistrationPolicy,
|
||||||
|
allowedEmailDomains: originForm.allowedEmailDomains,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
<p style={{textAlign: 'left'}}>
|
const onSubmit: SubmitHandler<IAuthenticationForm> = data => {
|
||||||
<ActionLink
|
handleUpdateTenantSettings(
|
||||||
onClick={() => window.open('https://docs.astuto.io/category/oauth-configuration/', '_blank')}
|
data.emailRegistrationPolicy as TenantSettingEmailRegistrationPolicy,
|
||||||
icon={<LearnMoreIcon />}
|
data.allowedEmailDomains,
|
||||||
>
|
).then(res => {
|
||||||
{I18n.t('site_settings.authentication.learn_more')}
|
if (res?.status !== HttpStatus.OK) return;
|
||||||
</ActionLink>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<OAuthProvidersList
|
window.location.reload();
|
||||||
oAuths={oAuths.items}
|
});
|
||||||
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
};
|
||||||
handleToggleEnabledDefaultOAuth={handleToggleEnabledDefaultOAuth}
|
|
||||||
handleDeleteOAuth={handleDeleteOAuth}
|
const [isDirtyAllowedEmails, setIsDirtyAllowedEmails] = React.useState(false);
|
||||||
setPage={setPage}
|
|
||||||
setSelectedOAuth={setSelectedOAuth}
|
return (
|
||||||
|
<>
|
||||||
|
<Box customClass="authenticationIndexPage">
|
||||||
|
<h2>{ I18n.t('site_settings.authentication.title') }</h2>
|
||||||
|
|
||||||
|
<div className="emailRegistrationPolicy">
|
||||||
|
<h3>{ I18n.t('site_settings.authentication.email_registration_subtitle') }</h3>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} onChange={handleSubmit(onSubmit)}>
|
||||||
|
<div className="formGroup">
|
||||||
|
<label htmlFor="brandSetting">{ getLabel('tenant_setting', 'email_registration_policy') }</label>
|
||||||
|
<select
|
||||||
|
{...register('emailRegistrationPolicy')}
|
||||||
|
id="emailRegistrationPolicy"
|
||||||
|
className="selectPicker"
|
||||||
|
>
|
||||||
|
<option value={TENANT_SETTING_EMAIL_REGISTRATION_POLICY_ALL_ALLOWED}>
|
||||||
|
{ I18n.t('site_settings.authentication.email_registration_policy_all') }
|
||||||
|
</option>
|
||||||
|
<option value={TENANT_SETTING_EMAIL_REGISTRATION_POLICY_NONE_ALLOWED}>
|
||||||
|
{ I18n.t('site_settings.authentication.email_registration_policy_none') }
|
||||||
|
</option>
|
||||||
|
<option value={TENANT_SETTING_EMAIL_REGISTRATION_POLICY_CUSTOM_DOMAINS_ALLOWED}>
|
||||||
|
{ I18n.t('site_settings.authentication.email_registration_policy_custom') }
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
originForm.emailRegistrationPolicy === TENANT_SETTING_EMAIL_REGISTRATION_POLICY_CUSTOM_DOMAINS_ALLOWED &&
|
||||||
|
<>
|
||||||
|
<div className="formGroup">
|
||||||
|
<label htmlFor="allowedEmailDomains">{ getLabel('tenant_setting', 'allowed_email_domains') }</label>
|
||||||
|
|
||||||
|
<div style={{display: 'flex'}}>
|
||||||
|
<input
|
||||||
|
{...register('allowedEmailDomains')}
|
||||||
|
onChange={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDirtyAllowedEmails(e.target.value !== originForm.allowedEmailDomains);
|
||||||
|
}}
|
||||||
|
style={{marginRight: 8}}
|
||||||
|
id="allowedEmailDomains"
|
||||||
|
type="text"
|
||||||
|
className="formControl"
|
||||||
|
/>
|
||||||
|
<Button onClick={() => null} disabled={!isDirtyAllowedEmails}>
|
||||||
|
{I18n.t('common.buttons.update')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SmallMutedText>
|
||||||
|
{ I18n.t('site_settings.authentication.allowed_email_domains_help') }
|
||||||
|
</SmallMutedText>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<OAuthProvidersList
|
||||||
|
oAuths={oAuths.items}
|
||||||
|
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
||||||
|
handleToggleEnabledDefaultOAuth={handleToggleEnabledDefaultOAuth}
|
||||||
|
handleDeleteOAuth={handleDeleteOAuth}
|
||||||
|
setPage={setPage}
|
||||||
|
setSelectedOAuth={setSelectedOAuth}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<SiteSettingsInfoBox
|
||||||
|
areUpdating={oAuths.areLoading || isSubmitting}
|
||||||
|
error={oAuths.error || submitError}
|
||||||
|
areDirty={isDirtyAllowedEmails}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</>
|
||||||
|
);
|
||||||
<SiteSettingsInfoBox
|
}
|
||||||
areUpdating={oAuths.areLoading || isSubmitting}
|
|
||||||
error={oAuths.error || submitError}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default AuthenticationIndexPage;
|
export default AuthenticationIndexPage;
|
||||||
@@ -6,10 +6,13 @@ import { IOAuth } from '../../../interfaces/IOAuth';
|
|||||||
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
import { OAuthsState } from '../../../reducers/oAuthsReducer';
|
||||||
|
|
||||||
import AuthenticationFormPage from './AuthenticationFormPage';
|
import AuthenticationFormPage from './AuthenticationFormPage';
|
||||||
import AuthenticationIndexPage from './AuthenticationIndexPage';
|
import AuthenticationIndexPage, { IAuthenticationForm } from './AuthenticationIndexPage';
|
||||||
import { ISiteSettingsOAuthForm } from './OAuthForm';
|
import { ISiteSettingsOAuthForm } from './OAuthForm';
|
||||||
|
import { TenantSettingEmailRegistrationPolicy } from '../../../interfaces/ITenantSetting';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
originForm: IAuthenticationForm;
|
||||||
|
|
||||||
oAuths: OAuthsState;
|
oAuths: OAuthsState;
|
||||||
|
|
||||||
requestOAuths(): void;
|
requestOAuths(): void;
|
||||||
@@ -18,6 +21,7 @@ interface Props {
|
|||||||
onToggleEnabledOAuth(id: number, isEnabled: boolean, authenticityToken: string): void;
|
onToggleEnabledOAuth(id: number, isEnabled: boolean, authenticityToken: string): void;
|
||||||
onToggleEnabledDefaultOAuth(id: number, isEnabled: boolean, authenticityToken: string): void;
|
onToggleEnabledDefaultOAuth(id: number, isEnabled: boolean, authenticityToken: string): void;
|
||||||
onDeleteOAuth(id: number, authenticityToken: string): void;
|
onDeleteOAuth(id: number, authenticityToken: string): void;
|
||||||
|
onUpdateTenantSettings(emailRegistrationPolicy: TenantSettingEmailRegistrationPolicy, allowedEmailDomains: string, authenticityToken: string): Promise<any>;
|
||||||
|
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
submitError: string;
|
submitError: string;
|
||||||
@@ -28,6 +32,7 @@ interface Props {
|
|||||||
export type AuthenticationPages = 'index' | 'new' | 'edit';
|
export type AuthenticationPages = 'index' | 'new' | 'edit';
|
||||||
|
|
||||||
const AuthenticationSiteSettingsP = ({
|
const AuthenticationSiteSettingsP = ({
|
||||||
|
originForm,
|
||||||
oAuths,
|
oAuths,
|
||||||
requestOAuths,
|
requestOAuths,
|
||||||
onSubmitOAuth,
|
onSubmitOAuth,
|
||||||
@@ -35,6 +40,7 @@ const AuthenticationSiteSettingsP = ({
|
|||||||
onToggleEnabledOAuth,
|
onToggleEnabledOAuth,
|
||||||
onToggleEnabledDefaultOAuth,
|
onToggleEnabledDefaultOAuth,
|
||||||
onDeleteOAuth,
|
onDeleteOAuth,
|
||||||
|
onUpdateTenantSettings,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
submitError,
|
submitError,
|
||||||
authenticityToken,
|
authenticityToken,
|
||||||
@@ -68,13 +74,19 @@ const AuthenticationSiteSettingsP = ({
|
|||||||
onDeleteOAuth(id, authenticityToken);
|
onDeleteOAuth(id, authenticityToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateTenantSettings = (emailRegistrationPolicy: TenantSettingEmailRegistrationPolicy, allowedEmailDomains: string): Promise<any> => {
|
||||||
|
return onUpdateTenantSettings(emailRegistrationPolicy, allowedEmailDomains, authenticityToken);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
page === 'index' ?
|
page === 'index' ?
|
||||||
<AuthenticationIndexPage
|
<AuthenticationIndexPage
|
||||||
|
originForm={originForm}
|
||||||
oAuths={oAuths}
|
oAuths={oAuths}
|
||||||
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
handleToggleEnabledOAuth={handleToggleEnabledOAuth}
|
||||||
handleToggleEnabledDefaultOAuth={handleToggleEnabledDefaultOAuth}
|
handleToggleEnabledDefaultOAuth={handleToggleEnabledDefaultOAuth}
|
||||||
handleDeleteOAuth={handleDeleteOAuth}
|
handleDeleteOAuth={handleDeleteOAuth}
|
||||||
|
handleUpdateTenantSettings={handleUpdateTenantSettings}
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelectedOAuth={setSelectedOAuth}
|
setSelectedOAuth={setSelectedOAuth}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
|||||||
import Button from '../../common/Button';
|
import Button from '../../common/Button';
|
||||||
import { IOAuth } from '../../../interfaces/IOAuth';
|
import { IOAuth } from '../../../interfaces/IOAuth';
|
||||||
import OAuthProviderItem from './OAuthProviderItem';
|
import OAuthProviderItem from './OAuthProviderItem';
|
||||||
|
import ActionLink from '../../common/ActionLink';
|
||||||
|
import { LearnMoreIcon } from '../../common/Icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
oAuths: Array<IOAuth>;
|
oAuths: Array<IOAuth>;
|
||||||
@@ -31,6 +33,15 @@ const OAuthProvidersList = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p style={{textAlign: 'left'}}>
|
||||||
|
<ActionLink
|
||||||
|
onClick={() => window.open('https://docs.astuto.io/category/oauth-configuration/', '_blank')}
|
||||||
|
icon={<LearnMoreIcon />}
|
||||||
|
>
|
||||||
|
{I18n.t('site_settings.authentication.learn_more')}
|
||||||
|
</ActionLink>
|
||||||
|
</p>
|
||||||
|
|
||||||
<ul className="oAuthsList">
|
<ul className="oAuthsList">
|
||||||
{
|
{
|
||||||
oAuths.map((oAuth, i) => (
|
oAuths.map((oAuth, i) => (
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { Store } from 'redux';
|
|||||||
import AuthenticationSiteSettings from '../../../containers/AuthenticationSiteSettings';
|
import AuthenticationSiteSettings from '../../../containers/AuthenticationSiteSettings';
|
||||||
import createStoreHelper from '../../../helpers/createStore';
|
import createStoreHelper from '../../../helpers/createStore';
|
||||||
import { State } from '../../../reducers/rootReducer';
|
import { State } from '../../../reducers/rootReducer';
|
||||||
|
import { IAuthenticationForm } from './AuthenticationIndexPage';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
originForm: IAuthenticationForm;
|
||||||
authenticityToken: string;
|
authenticityToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ class AuthenticationSiteSettingsRoot extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<Provider store={this.store}>
|
<Provider store={this.store}>
|
||||||
<AuthenticationSiteSettings
|
<AuthenticationSiteSettings
|
||||||
|
originForm={this.props.originForm}
|
||||||
authenticityToken={this.props.authenticityToken}
|
authenticityToken={this.props.authenticityToken}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface ISiteSettingsGeneralForm {
|
|||||||
locale: string;
|
locale: string;
|
||||||
rootBoardId?: string;
|
rootBoardId?: string;
|
||||||
customDomain?: string;
|
customDomain?: string;
|
||||||
|
isPrivate: boolean;
|
||||||
allowAnonymousFeedback: boolean;
|
allowAnonymousFeedback: boolean;
|
||||||
feedbackApprovalPolicy: string;
|
feedbackApprovalPolicy: string;
|
||||||
showRoadmapInHeader: boolean;
|
showRoadmapInHeader: boolean;
|
||||||
@@ -52,6 +53,7 @@ interface Props {
|
|||||||
locale: string,
|
locale: string,
|
||||||
rootBoardId: number,
|
rootBoardId: number,
|
||||||
customDomain: string,
|
customDomain: string,
|
||||||
|
isPrivate: boolean,
|
||||||
allowAnonymousFeedback: boolean,
|
allowAnonymousFeedback: boolean,
|
||||||
feedbackApprovalPolicy: string,
|
feedbackApprovalPolicy: string,
|
||||||
showRoadmapInHeader: boolean,
|
showRoadmapInHeader: boolean,
|
||||||
@@ -86,6 +88,7 @@ const GeneralSiteSettingsP = ({
|
|||||||
locale: originForm.locale,
|
locale: originForm.locale,
|
||||||
rootBoardId: originForm.rootBoardId,
|
rootBoardId: originForm.rootBoardId,
|
||||||
customDomain: originForm.customDomain,
|
customDomain: originForm.customDomain,
|
||||||
|
isPrivate: originForm.isPrivate,
|
||||||
allowAnonymousFeedback: originForm.allowAnonymousFeedback,
|
allowAnonymousFeedback: originForm.allowAnonymousFeedback,
|
||||||
feedbackApprovalPolicy: originForm.feedbackApprovalPolicy,
|
feedbackApprovalPolicy: originForm.feedbackApprovalPolicy,
|
||||||
showRoadmapInHeader: originForm.showRoadmapInHeader,
|
showRoadmapInHeader: originForm.showRoadmapInHeader,
|
||||||
@@ -104,6 +107,7 @@ const GeneralSiteSettingsP = ({
|
|||||||
data.locale,
|
data.locale,
|
||||||
Number(data.rootBoardId),
|
Number(data.rootBoardId),
|
||||||
data.customDomain,
|
data.customDomain,
|
||||||
|
data.isPrivate,
|
||||||
data.allowAnonymousFeedback,
|
data.allowAnonymousFeedback,
|
||||||
data.feedbackApprovalPolicy,
|
data.feedbackApprovalPolicy,
|
||||||
data.showRoadmapInHeader,
|
data.showRoadmapInHeader,
|
||||||
@@ -254,6 +258,20 @@ const GeneralSiteSettingsP = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div id="privacy" className="settingsGroup">
|
||||||
|
<h4>{ I18n.t('site_settings.general.subtitle_privacy') }</h4>
|
||||||
|
|
||||||
|
<div className="formGroup">
|
||||||
|
<div className="checkboxSwitch">
|
||||||
|
<input {...register('isPrivate')} type="checkbox" id="is_private_checkbox" />
|
||||||
|
<label htmlFor="is_private_checkbox">{ getLabel('tenant_setting', 'is_private') }</label>
|
||||||
|
<SmallMutedText>
|
||||||
|
{ I18n.t('site_settings.general.is_private_help') }
|
||||||
|
</SmallMutedText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="moderation" className="settingsGroup">
|
<div id="moderation" className="settingsGroup">
|
||||||
<h4>{ I18n.t('site_settings.general.subtitle_moderation') }</h4>
|
<h4>{ I18n.t('site_settings.general.subtitle_moderation') }</h4>
|
||||||
|
|
||||||
@@ -361,6 +379,12 @@ const GeneralSiteSettingsP = ({
|
|||||||
areUpdating={areUpdating}
|
areUpdating={areUpdating}
|
||||||
error={error}
|
error={error}
|
||||||
areDirty={isDirty && !isSubmitSuccessful}
|
areDirty={isDirty && !isSubmitSuccessful}
|
||||||
|
isSticky={isDirty && !isSubmitSuccessful}
|
||||||
|
saveButton={
|
||||||
|
<Button onClick={handleSubmit(onSubmit)} disabled={!isDirty}>
|
||||||
|
{I18n.t('common.buttons.update')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,18 @@ interface Props {
|
|||||||
areUpdating: boolean;
|
areUpdating: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
areDirty?: boolean;
|
areDirty?: boolean;
|
||||||
|
isSticky?: boolean;
|
||||||
|
saveButton?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SiteSettingsInfoBox = ({ areUpdating, error, areDirty = false }: Props) => (
|
const SiteSettingsInfoBox = ({
|
||||||
<Box customClass="siteSettingsInfo">
|
areUpdating,
|
||||||
|
error,
|
||||||
|
areDirty = false,
|
||||||
|
isSticky = false,
|
||||||
|
saveButton = null,
|
||||||
|
}: Props) => (
|
||||||
|
<Box customClass={`siteSettingsInfo${isSticky ? ' siteSettingsInfoSticky' : ''}`}>
|
||||||
{
|
{
|
||||||
areUpdating ?
|
areUpdating ?
|
||||||
<Spinner />
|
<Spinner />
|
||||||
@@ -22,7 +30,10 @@ const SiteSettingsInfoBox = ({ areUpdating, error, areDirty = false }: Props) =>
|
|||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
areDirty ?
|
areDirty ?
|
||||||
<span className="warning">{ I18n.t('site_settings.info_box.dirty') }</span>
|
<>
|
||||||
|
<span className="warning">{ I18n.t('site_settings.info_box.dirty') }</span>
|
||||||
|
{saveButton && saveButton}
|
||||||
|
</>
|
||||||
:
|
:
|
||||||
<span>{ I18n.t('site_settings.info_box.up_to_date') }</span>
|
<span>{ I18n.t('site_settings.info_box.up_to_date') }</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { ISiteSettingsOAuthForm } from "../components/SiteSettings/Authenticatio
|
|||||||
import { IOAuth } from "../interfaces/IOAuth";
|
import { IOAuth } from "../interfaces/IOAuth";
|
||||||
import { State } from "../reducers/rootReducer";
|
import { State } from "../reducers/rootReducer";
|
||||||
import { updateDefaultOAuth } from "../actions/OAuth/updateDefaultOAuth";
|
import { updateDefaultOAuth } from "../actions/OAuth/updateDefaultOAuth";
|
||||||
|
import { TenantSettingEmailRegistrationPolicy } from "../interfaces/ITenantSetting";
|
||||||
|
import { updateTenant } from "../actions/Tenant/updateTenant";
|
||||||
|
|
||||||
const mapStateToProps = (state: State) => ({
|
const mapStateToProps = (state: State) => ({
|
||||||
oAuths: state.oAuths,
|
oAuths: state.oAuths,
|
||||||
@@ -41,6 +43,20 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
onDeleteOAuth(id: number, authenticityToken: string) {
|
onDeleteOAuth(id: number, authenticityToken: string) {
|
||||||
dispatch(deleteOAuth(id, authenticityToken));
|
dispatch(deleteOAuth(id, authenticityToken));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUpdateTenantSettings(
|
||||||
|
emailRegistrationPolicy: TenantSettingEmailRegistrationPolicy,
|
||||||
|
allowedEmailDomains: string,
|
||||||
|
authenticityToken: string,
|
||||||
|
): Promise<any> {
|
||||||
|
return dispatch(updateTenant({
|
||||||
|
tenantSetting: {
|
||||||
|
email_registration_policy: emailRegistrationPolicy,
|
||||||
|
allowed_email_domains: allowedEmailDomains,
|
||||||
|
},
|
||||||
|
authenticityToken,
|
||||||
|
}));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
locale: string,
|
locale: string,
|
||||||
rootBoardId: number,
|
rootBoardId: number,
|
||||||
customDomain: string,
|
customDomain: string,
|
||||||
|
isPrivate: boolean,
|
||||||
allowAnonymousFeedback: boolean,
|
allowAnonymousFeedback: boolean,
|
||||||
feedbackApprovalPolicy: TenantSettingFeedbackApprovalPolicy,
|
feedbackApprovalPolicy: TenantSettingFeedbackApprovalPolicy,
|
||||||
showRoadmapInHeader: boolean,
|
showRoadmapInHeader: boolean,
|
||||||
@@ -33,6 +34,7 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||||||
tenantSetting: {
|
tenantSetting: {
|
||||||
brand_display: brandDisplaySetting,
|
brand_display: brandDisplaySetting,
|
||||||
root_board_id: rootBoardId,
|
root_board_id: rootBoardId,
|
||||||
|
is_private: isPrivate,
|
||||||
allow_anonymous_feedback: allowAnonymousFeedback,
|
allow_anonymous_feedback: allowAnonymousFeedback,
|
||||||
feedback_approval_policy: feedbackApprovalPolicy,
|
feedback_approval_policy: feedbackApprovalPolicy,
|
||||||
show_roadmap_in_header: showRoadmapInHeader,
|
show_roadmap_in_header: showRoadmapInHeader,
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ export type TenantSettingBrandDisplay =
|
|||||||
typeof TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY |
|
typeof TENANT_SETTING_BRAND_DISPLAY_LOGO_ONLY |
|
||||||
typeof TENANT_SETTING_BRAND_DISPLAY_NONE;
|
typeof TENANT_SETTING_BRAND_DISPLAY_NONE;
|
||||||
|
|
||||||
|
// Email registration policy
|
||||||
|
export const TENANT_SETTING_EMAIL_REGISTRATION_POLICY_ALL_ALLOWED = 'all_allowed';
|
||||||
|
export const TENANT_SETTING_EMAIL_REGISTRATION_POLICY_NONE_ALLOWED = 'none_allowed';
|
||||||
|
export const TENANT_SETTING_EMAIL_REGISTRATION_POLICY_CUSTOM_DOMAINS_ALLOWED = 'custom_domains_allowed';
|
||||||
|
|
||||||
|
export type TenantSettingEmailRegistrationPolicy =
|
||||||
|
typeof TENANT_SETTING_EMAIL_REGISTRATION_POLICY_ALL_ALLOWED |
|
||||||
|
typeof TENANT_SETTING_EMAIL_REGISTRATION_POLICY_NONE_ALLOWED |
|
||||||
|
typeof TENANT_SETTING_EMAIL_REGISTRATION_POLICY_CUSTOM_DOMAINS_ALLOWED;
|
||||||
|
|
||||||
// Feedback approval policy
|
// Feedback approval policy
|
||||||
export const TENANT_SETTING_FEEDBACK_APPROVAL_POLICY_ANONYMOUS_REQUIRE_APPROVAL = 'anonymous_require_approval';
|
export const TENANT_SETTING_FEEDBACK_APPROVAL_POLICY_ANONYMOUS_REQUIRE_APPROVAL = 'anonymous_require_approval';
|
||||||
export const TENANT_SETTING_FEEDBACK_APPROVAL_POLICY_NEVER_REQUIRE_APPROVAL = 'never_require_approval';
|
export const TENANT_SETTING_FEEDBACK_APPROVAL_POLICY_NEVER_REQUIRE_APPROVAL = 'never_require_approval';
|
||||||
@@ -32,6 +42,9 @@ export type TenantSettingCollapseBoardsInHeader =
|
|||||||
interface ITenantSetting {
|
interface ITenantSetting {
|
||||||
brand_display?: TenantSettingBrandDisplay;
|
brand_display?: TenantSettingBrandDisplay;
|
||||||
root_board_id?: number;
|
root_board_id?: number;
|
||||||
|
is_private?: boolean;
|
||||||
|
email_registration_policy?: TenantSettingEmailRegistrationPolicy;
|
||||||
|
allowed_email_domains?: string;
|
||||||
allow_anonymous_feedback?: boolean;
|
allow_anonymous_feedback?: boolean;
|
||||||
feedback_approval_policy?: TenantSettingFeedbackApprovalPolicy;
|
feedback_approval_policy?: TenantSettingFeedbackApprovalPolicy;
|
||||||
show_vote_count?: boolean;
|
show_vote_count?: boolean;
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ class TenantSetting < ApplicationRecord
|
|||||||
:always_collapse
|
:always_collapse
|
||||||
]
|
]
|
||||||
|
|
||||||
|
enum email_registration_policy: [
|
||||||
|
:all_allowed,
|
||||||
|
:none_allowed,
|
||||||
|
:custom_domains_allowed
|
||||||
|
]
|
||||||
|
|
||||||
enum feedback_approval_policy: [
|
enum feedback_approval_policy: [
|
||||||
:anonymous_require_approval,
|
:anonymous_require_approval,
|
||||||
:never_require_approval,
|
:never_require_approval,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ class TenantSettingPolicy < ApplicationPolicy
|
|||||||
[
|
[
|
||||||
:brand_display,
|
:brand_display,
|
||||||
:root_board_id,
|
:root_board_id,
|
||||||
|
:is_private,
|
||||||
|
:email_registration_policy,
|
||||||
|
:allowed_email_domains,
|
||||||
:allow_anonymous_feedback,
|
:allow_anonymous_feedback,
|
||||||
:feedback_approval_policy,
|
:feedback_approval_policy,
|
||||||
:show_vote_count,
|
:show_vote_count,
|
||||||
|
|||||||
@@ -4,42 +4,46 @@
|
|||||||
|
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= render "devise/shared/error_messages", resource: resource %>
|
||||||
|
|
||||||
<div class="form-group">
|
<% unless Current.tenant.tenant_setting.email_registration_policy == "none_allowed" %>
|
||||||
<%= f.label :full_name, class: "sr-only" %>
|
<div class="form-group">
|
||||||
<%= f.text_field :full_name,
|
<%= f.label :full_name, class: "sr-only" %>
|
||||||
placeholder: t('common.forms.auth.full_name'),
|
<%= f.text_field :full_name,
|
||||||
required: true,
|
placeholder: t('common.forms.auth.full_name'),
|
||||||
class: "form-control" %>
|
required: true,
|
||||||
</div>
|
class: "form-control" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :email, class: "sr-only" %>
|
<%= f.label :email, class: "sr-only" %>
|
||||||
<%= f.email_field :email,
|
<%= f.email_field :email,
|
||||||
autocomplete: "email",
|
autocomplete: "email",
|
||||||
placeholder: t('common.forms.auth.email'),
|
placeholder: t('common.forms.auth.email'),
|
||||||
required: true,
|
required: true,
|
||||||
class: "form-control" %>
|
class: "form-control" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :password, class: "sr-only" %>
|
<%= f.label :password, class: "sr-only" %>
|
||||||
<%= f.password_field :password,
|
<%= f.password_field :password,
|
||||||
placeholder: t('common.forms.auth.password'),
|
placeholder: t('common.forms.auth.password'),
|
||||||
required: true,
|
required: true,
|
||||||
class: "form-control" %>
|
class: "form-control" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :password_confirmation, class: "sr-only" %>
|
<%= f.label :password_confirmation, class: "sr-only" %>
|
||||||
<%= f.password_field :password_confirmation,
|
<%= f.password_field :password_confirmation,
|
||||||
placeholder: t('common.forms.auth.password_confirmation'),
|
placeholder: t('common.forms.auth.password_confirmation'),
|
||||||
required: true,
|
required: true,
|
||||||
class: "form-control" %>
|
class: "form-control" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<%= f.submit t('common.forms.auth.sign_up'), class: "btnPrimary btn-block" %>
|
<%= f.submit t('common.forms.auth.sign_up'), class: "btnPrimary btn-block" %>
|
||||||
</div>
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<p><%= t('common.forms.auth.email_registration_not_allowed') %></p>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render "devise/shared/o_auths", is_sign_up: true %>
|
<%= render "devise/shared/o_auths", is_sign_up: true %>
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
react_component(
|
react_component(
|
||||||
'SiteSettings/Authentication',
|
'SiteSettings/Authentication',
|
||||||
{
|
{
|
||||||
|
originForm: {
|
||||||
|
emailRegistrationPolicy: @tenant_setting.email_registration_policy,
|
||||||
|
allowedEmailDomains: @tenant_setting.allowed_email_domains,
|
||||||
|
},
|
||||||
authenticityToken: form_authenticity_token
|
authenticityToken: form_authenticity_token
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
showPoweredBy: @tenant_setting.show_powered_by,
|
showPoweredBy: @tenant_setting.show_powered_by,
|
||||||
rootBoardId: @tenant_setting.root_board_id.to_s,
|
rootBoardId: @tenant_setting.root_board_id.to_s,
|
||||||
customDomain: @tenant.custom_domain,
|
customDomain: @tenant.custom_domain,
|
||||||
|
isPrivate: @tenant_setting.is_private,
|
||||||
allowAnonymousFeedback: @tenant_setting.allow_anonymous_feedback,
|
allowAnonymousFeedback: @tenant_setting.allow_anonymous_feedback,
|
||||||
feedbackApprovalPolicy: @tenant_setting.feedback_approval_policy,
|
feedbackApprovalPolicy: @tenant_setting.feedback_approval_policy,
|
||||||
showRoadmapInHeader: @tenant_setting.show_roadmap_in_header,
|
showRoadmapInHeader: @tenant_setting.show_roadmap_in_header,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ en:
|
|||||||
errors:
|
errors:
|
||||||
unknown: 'An unknown error occurred'
|
unknown: 'An unknown error occurred'
|
||||||
unauthorized: 'You are not authorized'
|
unauthorized: 'You are not authorized'
|
||||||
|
email_domain_not_allowed: 'You cannot register with the provided email address'
|
||||||
not_logged_in: 'You must be logged in to access this page'
|
not_logged_in: 'You must be logged in to access this page'
|
||||||
not_enough_privileges: 'You do not have the privilegies to access this page'
|
not_enough_privileges: 'You do not have the privilegies to access this page'
|
||||||
user_blocked_or_deleted: 'You cannot access your account because it has been blocked or deleted.'
|
user_blocked_or_deleted: 'You cannot access your account because it has been blocked or deleted.'
|
||||||
@@ -124,6 +125,9 @@ en:
|
|||||||
custom_domain: 'Custom domain'
|
custom_domain: 'Custom domain'
|
||||||
tenant_setting:
|
tenant_setting:
|
||||||
brand_display: 'Display'
|
brand_display: 'Display'
|
||||||
|
is_private: 'Private site'
|
||||||
|
email_registration_policy: 'Email registration policy'
|
||||||
|
allowed_email_domains: 'Allowed email domains'
|
||||||
allow_anonymous_feedback: 'Allow anonymous feedback'
|
allow_anonymous_feedback: 'Allow anonymous feedback'
|
||||||
feedback_approval_policy: 'Feedback approval policy'
|
feedback_approval_policy: 'Feedback approval policy'
|
||||||
show_vote_count: 'Show vote count to users'
|
show_vote_count: 'Show vote count to users'
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ en:
|
|||||||
resend_unlock_instructions: 'Resend unlock instructions'
|
resend_unlock_instructions: 'Resend unlock instructions'
|
||||||
change_password: 'Change password'
|
change_password: 'Change password'
|
||||||
password_help: '%{count} characters minimum'
|
password_help: '%{count} characters minimum'
|
||||||
|
email_registration_not_allowed: 'Email registration not allowed'
|
||||||
comments_number:
|
comments_number:
|
||||||
one: '1 comment'
|
one: '1 comment'
|
||||||
other: '%{count} comments'
|
other: '%{count} comments'
|
||||||
@@ -181,8 +182,10 @@ en:
|
|||||||
brand_setting_name: 'Name only'
|
brand_setting_name: 'Name only'
|
||||||
brand_setting_logo: 'Logo only'
|
brand_setting_logo: 'Logo only'
|
||||||
brand_setting_none: 'None'
|
brand_setting_none: 'None'
|
||||||
|
subtitle_privacy: 'Privacy'
|
||||||
|
is_private_help: 'If you enable this setting, only logged in users will be able to see the content of the feedback space.'
|
||||||
subtitle_moderation: 'Moderation'
|
subtitle_moderation: 'Moderation'
|
||||||
allow_anonymous_feedback_help: 'Unregistered users will be able to submit feedback.'
|
allow_anonymous_feedback_help: 'If you enable this setting, unregistered users will be able to submit feedback.'
|
||||||
feedback_approval_policy_anonymous_require_approval: 'Require approval for anonymous feedback only'
|
feedback_approval_policy_anonymous_require_approval: 'Require approval for anonymous feedback only'
|
||||||
feedback_approval_policy_never_require_approval: 'Never require approval'
|
feedback_approval_policy_never_require_approval: 'Never require approval'
|
||||||
feedback_approval_policy_always_require_approval: 'Always require approval'
|
feedback_approval_policy_always_require_approval: 'Always require approval'
|
||||||
@@ -219,6 +222,11 @@ en:
|
|||||||
authentication:
|
authentication:
|
||||||
title: 'Authentication'
|
title: 'Authentication'
|
||||||
learn_more: 'Learn how to configure custom OAuth providers'
|
learn_more: 'Learn how to configure custom OAuth providers'
|
||||||
|
email_registration_subtitle: 'Email registration'
|
||||||
|
email_registration_policy_all: 'Anyone can register with email'
|
||||||
|
email_registration_policy_none: 'No one can register with email'
|
||||||
|
email_registration_policy_custom: 'Only users with certain email domains can register with email'
|
||||||
|
allowed_email_domains_help: 'Separate domains with commas. Example: "gmail.com,yahoo.com,hotmail.com". Leave blank to allow any domain.'
|
||||||
oauth_subtitle: 'OAuth providers'
|
oauth_subtitle: 'OAuth providers'
|
||||||
default_oauth: 'Default OAuth provider'
|
default_oauth: 'Default OAuth provider'
|
||||||
copy_url: 'Copy URL'
|
copy_url: 'Copy URL'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddIsPrivateToTenantSettings < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :tenant_settings, :is_private, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class AddEmailRegistrationPolicyToTenantSettings < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :tenant_settings, :email_registration_policy, :integer, default: 0, null: false
|
||||||
|
add_column :tenant_settings, :allowed_email_domains, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2024_07_08_191938) do
|
ActiveRecord::Schema.define(version: 2024_08_21_133530) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -168,6 +168,9 @@ ActiveRecord::Schema.define(version: 2024_07_08_191938) do
|
|||||||
t.boolean "show_powered_by", default: true, null: false
|
t.boolean "show_powered_by", default: true, null: false
|
||||||
t.boolean "allow_anonymous_feedback", default: true, null: false
|
t.boolean "allow_anonymous_feedback", default: true, null: false
|
||||||
t.integer "feedback_approval_policy", default: 0, null: false
|
t.integer "feedback_approval_policy", default: 0, null: false
|
||||||
|
t.boolean "is_private", default: false, null: false
|
||||||
|
t.integer "email_registration_policy", default: 0, null: false
|
||||||
|
t.string "allowed_email_domains"
|
||||||
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
t.index ["tenant_id"], name: "index_tenant_settings_on_tenant_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :tenant_setting do
|
factory :tenant_setting do
|
||||||
tenant
|
tenant
|
||||||
|
is_private { false }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,4 +60,24 @@ RSpec.describe TenantSetting, type: :model do
|
|||||||
tenant_setting.feedback_approval_policy = 'always_require_approval'
|
tenant_setting.feedback_approval_policy = 'always_require_approval'
|
||||||
expect(tenant_setting).to be_valid
|
expect(tenant_setting).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'has a setting for making the site private' do
|
||||||
|
expect(tenant_setting.is_private).to be_falsey
|
||||||
|
|
||||||
|
tenant_setting.is_private = true
|
||||||
|
expect(tenant_setting).to be_valid
|
||||||
|
|
||||||
|
tenant_setting.is_private = false
|
||||||
|
expect(tenant_setting).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a setting for email registration policy' do
|
||||||
|
expect(tenant_setting.email_registration_policy).to eq('all_allowed')
|
||||||
|
|
||||||
|
tenant_setting.email_registration_policy = 'none_allowed'
|
||||||
|
expect(tenant_setting).to be_valid
|
||||||
|
|
||||||
|
tenant_setting.email_registration_policy = 'custom_domains_allowed'
|
||||||
|
expect(tenant_setting).to be_valid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ feature 'site settings: general', type: :system, js: true do
|
|||||||
|
|
||||||
fill_in 'Site name', with: new_site_name
|
fill_in 'Site name', with: new_site_name
|
||||||
fill_in 'Site logo', with: new_site_logo
|
fill_in 'Site logo', with: new_site_logo
|
||||||
click_button 'Save'
|
find('button', text: 'Save', match: :first).click
|
||||||
|
|
||||||
within '.siteSettingsInfo' do
|
within '.siteSettingsInfo' do
|
||||||
expect(page).to have_content('All changes saved')
|
expect(page).to have_content('All changes saved')
|
||||||
@@ -43,7 +43,7 @@ feature 'site settings: general', type: :system, js: true do
|
|||||||
expect(Current.tenant.locale).not_to eq(new_site_language)
|
expect(Current.tenant.locale).not_to eq(new_site_language)
|
||||||
|
|
||||||
select_by_value 'locale', new_site_language
|
select_by_value 'locale', new_site_language
|
||||||
click_button 'Save'
|
find('button', text: 'Save', match: :first).click
|
||||||
|
|
||||||
within '.siteSettingsInfo' do
|
within '.siteSettingsInfo' do
|
||||||
expect(page).to have_content('Tutte le modifiche sono state salvate')
|
expect(page).to have_content('Tutte le modifiche sono state salvate')
|
||||||
|
|||||||
Reference in New Issue
Block a user