diff --git a/backend/src/api/auth/authSignUp.ts b/backend/src/api/auth/authSignUp.ts index 67f261120b..51faac3a3d 100644 --- a/backend/src/api/auth/authSignUp.ts +++ b/backend/src/api/auth/authSignUp.ts @@ -1,6 +1,9 @@ import AuthService from '../../services/auth/authService' export default async (req, res) => { + if (!req.body.acceptedTermsAndPrivacy) { + return res.status(422).send({ error: 'Please accept terms of service and privacy policy' }) + } const payload = await AuthService.signup( req.body.email, req.body.password, @@ -8,8 +11,9 @@ export default async (req, res) => { req.body.tenantId, req.body.firstName, req.body.lastName, + req.body.acceptedTermsAndPrivacy, req, ) - await req.responseHandler.success(req, res, payload) + return req.responseHandler.success(req, res, payload) } diff --git a/backend/src/database/migrations/U1689235178__addTermsAndPrivacyColumn.sql b/backend/src/database/migrations/U1689235178__addTermsAndPrivacyColumn.sql new file mode 100644 index 0000000000..54d92a34c9 --- /dev/null +++ b/backend/src/database/migrations/U1689235178__addTermsAndPrivacyColumn.sql @@ -0,0 +1 @@ +ALTER TABLE public."users" DROP COLUMN "acceptedTermsAndPrivacy"; diff --git a/backend/src/database/migrations/V1689235178__addTermsAndPrivacyColumn.sql b/backend/src/database/migrations/V1689235178__addTermsAndPrivacyColumn.sql new file mode 100644 index 0000000000..fdd4b789c6 --- /dev/null +++ b/backend/src/database/migrations/V1689235178__addTermsAndPrivacyColumn.sql @@ -0,0 +1 @@ +ALTER TABLE public."users" ADD COLUMN "acceptedTermsAndPrivacy" BOOLEAN DEFAULT true; \ No newline at end of file diff --git a/backend/src/database/models/user.ts b/backend/src/database/models/user.ts index a64482691b..2deb3ab223 100644 --- a/backend/src/database/models/user.ts +++ b/backend/src/database/models/user.ts @@ -95,6 +95,10 @@ export default (sequelize, DataTypes) => { jwtTokenInvalidBefore: { type: DataTypes.DATE, }, + acceptedTermsAndPrivacy: { + type: DataTypes.BOOLEAN, + defaultValue: true, + }, importHash: { type: DataTypes.STRING(255), allowNull: true, diff --git a/backend/src/database/repositories/userRepository.ts b/backend/src/database/repositories/userRepository.ts index a121c31024..9453f51b27 100644 --- a/backend/src/database/repositories/userRepository.ts +++ b/backend/src/database/repositories/userRepository.ts @@ -104,6 +104,7 @@ export default class UserRepository { lastName: data.lastName, fullName: data.fullName, password: data.password, + acceptedTermsAndPrivacy: data.acceptedTermsAndPrivacy, }, { transaction }, ) @@ -142,6 +143,7 @@ export default class UserRepository { firstName: data.firstName || null, lastName: data.lastName || null, phoneNumber: data.phoneNumber || null, + acceptedTermsAndPrivacy: data.acceptedTermsAndPrivacy || false, updatedById: currentUser.id, }, { transaction }, @@ -662,6 +664,7 @@ export default class UserRepository { firstName, lastName, fullName, + acceptedTermsAndPrivacy: false, } const transaction = SequelizeRepository.getTransaction(options) diff --git a/backend/src/services/auth/authService.ts b/backend/src/services/auth/authService.ts index 20e142ccc0..e72f19acac 100644 --- a/backend/src/services/auth/authService.ts +++ b/backend/src/services/auth/authService.ts @@ -28,6 +28,7 @@ class AuthService { tenantId, firstName, lastName, + acceptedTermsAndPrivacy, options: any = {}, ) { const transaction = await SequelizeRepository.createTransaction(options) @@ -126,6 +127,7 @@ class AuthService { fullName, password: hashedPassword, email, + acceptedTermsAndPrivacy, }, { ...options, diff --git a/frontend/src/i18n/en.js b/frontend/src/i18n/en.js index 8786715187..3f16a20b05 100644 --- a/frontend/src/i18n/en.js +++ b/frontend/src/i18n/en.js @@ -570,6 +570,7 @@ const en = { fullName: 'Name', firstName: 'First name', lastName: 'Last name', + acceptedTermsAndPrivacy: 'Terms and privacy', status: 'Status', phoneNumber: 'Phone Number', role: 'Role', diff --git a/frontend/src/middleware/auth/auth-guard.js b/frontend/src/middleware/auth/auth-guard.js index 40182b11d6..0fe7a4fc46 100644 --- a/frontend/src/middleware/auth/auth-guard.js +++ b/frontend/src/middleware/auth/auth-guard.js @@ -28,9 +28,11 @@ export default async function ({ to, store, router }) { } await store.dispatch('auth/doWaitUntilInit'); + const currentUser = store.getters['auth/currentUser']; + const permissionChecker = new PermissionChecker( store.getters['auth/currentTenant'], - store.getters['auth/currentUser'], + currentUser, ); if (!permissionChecker.isAuthenticated) { @@ -57,6 +59,11 @@ export default async function ({ to, store, router }) { router.push('/403'); return; } + console.log(currentUser); + if (!currentUser.acceptedTermsAndPrivacy) { + router.push({ path: '/auth/terms-and-privacy' }); + return; + } if ( ['multi', 'multi-with-subdomain'].includes( diff --git a/frontend/src/modules/auth/auth-routes.js b/frontend/src/modules/auth/auth-routes.js index 8429282842..050a7c7778 100644 --- a/frontend/src/modules/auth/auth-routes.js +++ b/frontend/src/modules/auth/auth-routes.js @@ -13,6 +13,7 @@ const ProfileFormPage = () => import('@/modules/auth/pages/profile-form-page.vue const PasswordResetPage = () => import('@/modules/auth/pages/password-reset-page.vue'); const VerifyEmailPage = () => import('@/modules/auth/pages/verify-email-page.vue'); const InvitationPage = () => import('@/modules/auth/pages/invitation-page.vue'); +const TermsAndPrivacyPage = () => import('@/modules/auth/pages/terms-and-privacy.vue'); const EmptyPermissionsPage = () => import('@/modules/auth/pages/empty-permissions-page.vue'); export default [ @@ -69,6 +70,12 @@ export default [ component: InvitationPage, meta: { title: 'Invitation' }, }, + { + name: 'terms-and-privacy', + path: 'terms-and-privacy', + component: TermsAndPrivacyPage, + meta: { title: 'Terms of service and privacy policy' }, + }, ], }, { diff --git a/frontend/src/modules/auth/auth-service.js b/frontend/src/modules/auth/auth-service.js index df63a5cc90..ad32d53c0a 100644 --- a/frontend/src/modules/auth/auth-service.js +++ b/frontend/src/modules/auth/auth-service.js @@ -15,20 +15,10 @@ export class AuthService { .then((response) => response.data); } - static sendPasswordResetEmail(email) { - return authAxios - .post('/auth/send-password-reset-email', { - email, - tenantId: tenantSubdomain.isSubdomain - ? AuthCurrentTenant.get() - : undefined, - }) - .then((response) => response.data); - } - static registerWithEmailAndPassword( email, password, + acceptedTermsAndPrivacy, data = {}, ) { const invitationToken = AuthInvitationToken.get(); @@ -39,6 +29,7 @@ export class AuthService { password, invitationToken, ...data, + acceptedTermsAndPrivacy, tenantId: tenantSubdomain.isSubdomain ? AuthCurrentTenant.get() : undefined, @@ -50,6 +41,17 @@ export class AuthService { }); } + static sendPasswordResetEmail(email) { + return authAxios + .post('/auth/send-password-reset-email', { + email, + tenantId: tenantSubdomain.isSubdomain + ? AuthCurrentTenant.get() + : undefined, + }) + .then((response) => response.data); + } + static signinWithEmailAndPassword(email, password) { const invitationToken = AuthInvitationToken.get(); diff --git a/frontend/src/modules/auth/pages/signup-page.vue b/frontend/src/modules/auth/pages/signup-page.vue index c02da342ef..eca7056e09 100644 --- a/frontend/src/modules/auth/pages/signup-page.vue +++ b/frontend/src/modules/auth/pages/signup-page.vue @@ -174,6 +174,23 @@ + + + I accept the terms of service + and privacy policy. + + +
+ + You have to accept terms of service and privacy policy before signing up +
+ this.doRegisterEmailAndPassword({ - email: this.model.email, - password: this.model.password, - data: { - firstName: this.model.firstName, - lastName: this.model.lastName, - }, - })); + this.acceptTerms = false; + this.$refs.form.validate().then(() => { + if (this.model.acceptedTermsAndPrivacy) { + this.doRegisterEmailAndPassword({ + email: this.model.email, + password: this.model.password, + data: { + firstName: this.model.firstName, + lastName: this.model.lastName, + }, + acceptedTermsAndPrivacy: this.model.acceptedTermsAndPrivacy, + }); + } else { + this.acceptTerms = true; + } + }); }, socialOauthLink(provider) { diff --git a/frontend/src/modules/auth/pages/terms-and-privacy.vue b/frontend/src/modules/auth/pages/terms-and-privacy.vue new file mode 100644 index 0000000000..6673cad34e --- /dev/null +++ b/frontend/src/modules/auth/pages/terms-and-privacy.vue @@ -0,0 +1,100 @@ + + + diff --git a/frontend/src/modules/auth/store/actions.js b/frontend/src/modules/auth/store/actions.js index a5b9143570..527a35afe0 100644 --- a/frontend/src/modules/auth/store/actions.js +++ b/frontend/src/modules/auth/store/actions.js @@ -87,12 +87,15 @@ export default { doRegisterEmailAndPassword( { commit }, - { email, password, data = {} }, + { + email, password, data = {}, acceptedTermsAndPrivacy, + }, ) { commit('AUTH_START'); return AuthService.registerWithEmailAndPassword( email, password, + acceptedTermsAndPrivacy, data, ) .then((token) => { diff --git a/frontend/src/modules/user/user-model.js b/frontend/src/modules/user/user-model.js index 557024fc92..9bf12f9e55 100644 --- a/frontend/src/modules/user/user-model.js +++ b/frontend/src/modules/user/user-model.js @@ -127,6 +127,13 @@ const fields = { label('createdAtRange'), ), roleUser: new GenericField('roleUser', label('roleUser')), + acceptedTermsAndPrivacy: new BooleanField( + 'acceptedTermsAndPrivacy', + label('acceptedTermsAndPrivacy'), + { + required: true, + }, + ), }; export class UserModel extends GenericModel {