Skip to content

Commit

Permalink
[PLA-1766] require password settings (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
zlayine authored May 20, 2024
1 parent e5c276c commit 59efaff
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 20 deletions.
8 changes: 6 additions & 2 deletions resources/js/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,23 @@ export class AuthApi {
return AuthApi.sendPlatfromRequest(data);
}

static async deleteAccount() {
static async deleteAccount(password: string) {
const data = {
query: mutations.DeleteAccount,
varaiables: {
password,
},
};

return AuthApi.sendPlatfromRequest(data);
}

static async updateUser(email: string) {
static async updateUser(email: string, password: string) {
const data = {
query: mutations.UpdateUser,
variables: {
email,
password,
},
};

Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/Modal.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<TransitionRoot appear :show="isOpen" as="template">
<Dialog as="div" @close="close" class="relative z-50">
<Dialog as="div" @close="close as any" class="relative z-50">
<TransitionChild
as="template"
enter="duration-300 ease-out"
Expand Down Expand Up @@ -40,7 +40,7 @@
<script setup lang="ts">
import { TransitionRoot, TransitionChild, Dialog, DialogPanel } from '@headlessui/vue';
withDefaults(defineProps<{ isOpen: boolean; close: (_close: boolean) => void; width?: string }>(), {
withDefaults(defineProps<{ isOpen: boolean; close: Function; width?: string }>(), {
width: 'max-w-md',
});
</script>
13 changes: 10 additions & 3 deletions resources/js/components/pages/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<p>Please complete these steps in order to use the platform:</p>
<div>Create an API token</div>
</div>
<div class="flex flex-col space-y-8">
<div class="flex flex-col space-y-4">
<div v-if="isMultiTenant" class="flex flex-col space-y-4">
<CollapseCard
dusk-id="apiTokensTab"
Expand Down Expand Up @@ -78,6 +78,11 @@
title="Delete account"
description="Your account will be deactivated and will be removed after 14 days, All active Beams or other pending platform actions will be permanently deleted. Do you want to delete your account?"
@closed="confirmModal = false"
@confirm="verifyPasswordModal = true"
/>
<VerifyPasswordModal
:is-open="verifyPasswordModal"
@closed="verifyPasswordModal = false"
@confirm="deleteAccount"
/>
</div>
Expand All @@ -100,13 +105,15 @@ import { AuthApi } from '~/api/auth';
import ConfirmModal from '../ConfirmModal.vue';
import { ApiService } from '~/api';
import SettingsChangeEmail from './SettingsChangeEmail.vue';
import VerifyPasswordModal from './VerifyPasswordModal.vue';
const router = useRouter();
const appStore = useAppStore();
const advancedMode = ref(appStore.advanced);
const loading = ref(appStore.user || !appStore.hasMultiTenantPackage ? false : true);
const confirmModal = ref(false);
const verifyPasswordModal = ref(false);
const tokens = computed(() => appStore.user?.apiTokens);
Expand All @@ -127,8 +134,8 @@ const formatName = (name: string) => {
return name.replaceAll('-', ' ');
};
const deleteAccount = async () => {
await AuthApi.deleteAccount();
const deleteAccount = async (password) => {
await AuthApi.deleteAccount(password);
appStore.clearLogin();
await ApiService.reloadCsrf();
};
Expand Down
13 changes: 12 additions & 1 deletion resources/js/components/pages/SettingsChangeEmail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<div v-else>
<Btn primary @click="enableReset = true" dusk="changeEmailBtn">Change Email</Btn>
</div>
<VerifyPasswordModal
:is-open="verifyPasswordModal"
@closed="verifyPasswordModal = false"
@confirm="confirmChangeEmail"
/>
</div>
</template>

Expand All @@ -44,6 +49,7 @@ import { snackbarErrors } from '~/util';
import snackbar from '~/util/snackbar';
import { Form } from 'vee-validate';
import { useAppStore } from '~/store';
import VerifyPasswordModal from './VerifyPasswordModal.vue';
const appStore = useAppStore();
Expand All @@ -52,6 +58,7 @@ const newEmail = ref();
const isLoading = ref(false);
const formRef = ref();
const enableReset = ref(false);
const verifyPasswordModal = ref(false);
const validation = yup.object().shape({
newEmail: yup.string().email().required(),
Expand All @@ -65,9 +72,13 @@ const isValid = async () => {
const changeEmail = async () => {
if (!(await isValid())) return;
verifyPasswordModal.value = true;
};
const confirmChangeEmail = async (password) => {
isLoading.value = true;
try {
const res = await AuthApi.updateUser(newEmail.value);
const res = await AuthApi.updateUser(newEmail.value, password);
if (res.data.UpdateUser) {
snackbar.success({
title: 'Email changed',
Expand Down
115 changes: 115 additions & 0 deletions resources/js/components/pages/VerifyPasswordModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<template>
<Modal :is-open="props.isOpen" :close="closeModal">
<DialogTitle
as="h3"
class="text-lg font-medium leading-6 text-light-content-strong dark:text-dark-content-strong"
>
Verify Password
</DialogTitle>
<div class="mt-2 space-y-4">
<p class="text-sm text-light-content dark:text-dark-content">
Please enter your password to verify your identity.
</p>
<FormInput
v-model="password"
label="Password"
placeholder="Password"
class="flex-1 sm:flex-none text-light-content dark:text-dark-content"
name="password"
type="password"
/>
<vue-recaptcha
v-if="hasCaptcha"
ref="captchaRef"
:style="{ visibility: isCaptchaBadgeVisible ? 'visible' : 'hidden' }"
size="invisible"
:load-recaptcha-script="false"
:sitekey="reCaptchaSiteKey"
@verify="confirm"
@expired="onCaptchaExpired"
/>
</div>

<div class="flex justify-end space-x-4 mt-6">
<Btn @click="closeModal">Cancel</Btn>
<Btn primary @click="verifyCaptcha">Confirm</Btn>
</div>
</Modal>
</template>

<script setup lang="ts">
import { DialogTitle } from '@headlessui/vue';
import Btn from '~/components/Btn.vue';
import Modal from '~/components/Modal.vue';
import FormInput from '../FormInput.vue';
import { ref } from 'vue';
import { useAppStore } from '~/store';
import { AuthApi } from '~/api/auth';
import { VueRecaptcha } from 'vue-recaptcha';
import snackbar from '~/util/snackbar';
const props = defineProps<{ isOpen: boolean }>();
const emit = defineEmits(['closed', 'confirm']);
const appStore = useAppStore();
const password = ref();
const isCaptchaBadgeVisible = ref(false);
const captchaRef = ref();
const hasCaptcha = window.bootstrap?.captcha_key?.length > 0;
const reCaptchaSiteKey = window.bootstrap?.captcha_key || 'null';
const confirm = async (recaptcha?: string) => {
const email = appStore.user?.email;
const res = await AuthApi.login(email, password.value, recaptcha);
if (res.data.Login) {
emit('confirm', password.value);
closeModal();
} else {
snackbar.error({
title: 'Error',
text: 'Invalid password',
});
}
};
const loadCaptchaScript = async () => {
if (!hasCaptcha) {
isCaptchaBadgeVisible.value = true;
return;
}
if (!document.getElementById('recaptcha-script')) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.id = 'recaptcha-script';
script.async = true;
script.defer = true;
script.src = 'https://www.google.com/recaptcha/api.js?onload=vueRecaptchaApiLoaded&render=explicit&hl=:1';
document.getElementsByTagName('head')[0].appendChild(script);
}
isCaptchaBadgeVisible.value = true;
};
const onCaptchaExpired = () => {
captchaRef.value.reset();
};
const verifyCaptcha = () => {
if (!hasCaptcha) {
return confirm();
}
captchaRef.value.execute();
};
const closeModal = () => {
emit('closed');
};
(() => {
loadCaptchaScript();
})();
</script>
4 changes: 2 additions & 2 deletions resources/js/graphql/mutation/auth/DeleteAccount.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default `mutation DeleteAccount {
DeleteAccount
export default `mutation DeleteAccount($password: String!) {
DeleteAccount(password: $password)
}`;
4 changes: 2 additions & 2 deletions resources/js/graphql/mutation/auth/UpdateUser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default `mutation UpdateUser($email: String) {
UpdateUser(email: $email)
export default `mutation UpdateUser($email: String, $password: String) {
UpdateUser(email: $email, password: $password)
}`;
10 changes: 2 additions & 8 deletions resources/js/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,8 @@ const routes = [
requiresAuth: true,
requiresToken: true,
},
redirect: { name: 'platform.beams.list' },
children: [
{
path: '',
redirect: { name: 'platform.beams.list' },
},
{
path: 'list',
name: 'platform.beams.list',
Expand Down Expand Up @@ -94,11 +91,8 @@ const routes = [
requiresAuth: true,
requiresToken: true,
},
redirect: { name: 'platform.marketplace.bids' },
children: [
{
path: '',
redirect: { name: 'platform.marketplace.bids' },
},
{
path: 'bids',
name: 'platform.marketplace.bids',
Expand Down

0 comments on commit 59efaff

Please sign in to comment.