Skip to content

Commit

Permalink
Terms and privacy (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaspergrom authored Jul 14, 2023
1 parent 8b2f03f commit 35657a1
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 22 deletions.
6 changes: 5 additions & 1 deletion backend/src/api/auth/authSignUp.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
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,
req.body.invitationToken,
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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE public."users" DROP COLUMN "acceptedTermsAndPrivacy";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE public."users" ADD COLUMN "acceptedTermsAndPrivacy" BOOLEAN DEFAULT true;
4 changes: 4 additions & 0 deletions backend/src/database/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions backend/src/database/repositories/userRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export default class UserRepository {
lastName: data.lastName,
fullName: data.fullName,
password: data.password,
acceptedTermsAndPrivacy: data.acceptedTermsAndPrivacy,
},
{ transaction },
)
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -662,6 +664,7 @@ export default class UserRepository {
firstName,
lastName,
fullName,
acceptedTermsAndPrivacy: false,
}

const transaction = SequelizeRepository.getTransaction(options)
Expand Down
2 changes: 2 additions & 0 deletions backend/src/services/auth/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class AuthService {
tenantId,
firstName,
lastName,
acceptedTermsAndPrivacy,
options: any = {},
) {
const transaction = await SequelizeRepository.createTransaction(options)
Expand Down Expand Up @@ -126,6 +127,7 @@ class AuthService {
fullName,
password: hashedPassword,
email,
acceptedTermsAndPrivacy,
},
{
...options,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/middleware/auth/auth-guard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/modules/auth/auth-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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' },
},
],
},
{
Expand Down
24 changes: 13 additions & 11 deletions frontend/src/modules/auth/auth-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -39,6 +29,7 @@ export class AuthService {
password,
invitationToken,
...data,
acceptedTermsAndPrivacy,
tenantId: tenantSubdomain.isSubdomain
? AuthCurrentTenant.get()
: undefined,
Expand All @@ -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();

Expand Down
44 changes: 36 additions & 8 deletions frontend/src/modules/auth/pages/signup-page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@
</template>
</el-form-item>

<el-checkbox
v-model="model[fields.acceptedTermsAndPrivacy.name]"
>
<span class="text-sm text-gray-900">
I accept the <a href="https://www.crowd.dev/terms-of-use" target="_blank" rel="noopener noreferrer">terms of service</a>
and <a href="https://www.crowd.dev/privacy-policy" target="_blank" rel="noopener noreferrer">privacy policy</a>.
</span>
</el-checkbox>
<div v-if="acceptTerms && !model[fields.acceptedTermsAndPrivacy.name]" class="flex items-center mt-1">
<i
class="h-4 flex items-center ri-error-warning-line text-base text-red-500"
/>
<span
class="pl-1 text-2xs text-red-500 leading-4.5"
>You have to accept terms of service and privacy policy before signing up</span>
</div>

<el-form-item class="pt-4 mb-0">
<el-button
id="submit"
Expand Down Expand Up @@ -246,12 +263,15 @@ export default {
password: fields.password.forFormRules(),
passwordConfirmation:
fields.passwordConfirmation.forFormRules(),
acceptedTermsAndPrivacy:
fields.passwordConfirmation.forFormRules(),
},
model: {},
display: {
password: false,
passwordConfirm: false,
},
acceptTerms: false,
};
},
Expand All @@ -275,14 +295,22 @@ export default {
...mapActions('auth', ['doRegisterEmailAndPassword']),
doSubmit() {
this.$refs.form.validate().then(() => 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) {
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/modules/auth/pages/terms-and-privacy.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<div>
<h3 class="text-2xl leading-12 font-semibold mb-1">
Accept terms of service & privacy policy
</h3>
<div class="pt-10">
<div class="pb-4">
<el-checkbox
id="remember-me"
v-model="model[fields.acceptedTermsAndPrivacy.name]"
>
<span class="text-sm text-gray-900" /><span class="text-sm text-gray-900"> I accept the <a
href="https://www.crowd.dev/terms-of-use"
target="_blank"
rel="noopener noreferrer"
>terms of service</a>
and <a
href="https://www.crowd.dev/privacy-policy"
target="_blank"
rel="noopener noreferrer"
>privacy policy</a>.
</span>
</el-checkbox>
<div v-if="acceptTerms && !model[fields.acceptedTermsAndPrivacy.name]" class="flex items-center mt-1">
<i
class="h-4 flex items-center ri-error-warning-line text-base text-red-500"
/>
<span
class="pl-1 text-2xs text-red-500 leading-4.5"
>You have to accept terms of service and privacy policy before continuing</span>
</div>
</div>

<el-button
id="submit"
:loading="loading"
:disabled="!model[fields.acceptedTermsAndPrivacy.name]"
native-type="submit"
class="w-full btn btn--primary btn--lg"
@click="doSubmit"
>
Continue
</el-button>
</div>
</div>
</template>

<script>
import { mapActions, mapGetters } from "vuex";
import { UserModel } from '@/modules/user/user-model';
import { AuthService } from '@/modules/auth/auth-service';
const { fields } = UserModel;
export default {
name: 'AppSignupPage',
data() {
return {
model: {},
acceptTerms: false,
loading: false,
};
},
computed: {
...mapGetters('auth', ['currentUser']),
fields() {
return fields;
},
},
mounted() {
if (!this.currentUser) {
this.$router.push('/auth/signin');
}
},
methods: {
...mapActions('auth', ['doRefreshCurrentUser']),
doSubmit() {
if (this.model.acceptedTermsAndPrivacy) {
this.loading = true;
AuthService.updateProfile({
acceptedTermsAndPrivacy: this.model.acceptedTermsAndPrivacy,
})
.then(() => this.doRefreshCurrentUser())
.then(() => {
this.$router.push('/');
})
.finally(() => {
this.loading = false;
});
} else {
this.acceptTerms = true;
}
},
},
};
</script>
5 changes: 4 additions & 1 deletion frontend/src/modules/auth/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/modules/user/user-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 35657a1

Please sign in to comment.