@sowell/quasar-app-extension-auth 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/boot/router-guards.ts +2 -1
- package/src/components/dialogs/Message.vue +15 -13
- package/src/components/dialogs/WebSendEmail.vue +2 -1
- package/src/components/dialogs/sw-info-dialog.vue +16 -10
- package/src/components/pages/AuthReporter.vue +23 -2
- package/src/components/pages/OtpValidation.vue +177 -0
- package/src/components/pages/WebAuth.vue +64 -38
- package/src/composables/auth.ts +13 -5
- package/src/composables/resetPassword.ts +66 -54
- package/src/injects/prompts.ts +4 -0
- package/src/prompts.js +21 -0
- package/src/routes/index.ts +9 -1
package/package.json
CHANGED
|
@@ -21,7 +21,8 @@ export default boot(({ app, router }: BootInterface) => {
|
|
|
21
21
|
if (
|
|
22
22
|
(!isValid || !token) &&
|
|
23
23
|
to.path !== LOCAL_LOGIN_ROUTE &&
|
|
24
|
-
to.name !== "update_password"
|
|
24
|
+
to.name !== "update_password" &&
|
|
25
|
+
to.name !== "otp_validation"
|
|
25
26
|
) {
|
|
26
27
|
next(LOCAL_LOGIN_ROUTE);
|
|
27
28
|
} else {
|
|
@@ -55,17 +55,19 @@
|
|
|
55
55
|
@click="retypeEmail"
|
|
56
56
|
class="full-width"
|
|
57
57
|
/>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
<slot>
|
|
59
|
+
<q-btn
|
|
60
|
+
class="q-mt-md full-width"
|
|
61
|
+
unelevated
|
|
62
|
+
style="background: #fff"
|
|
63
|
+
flat
|
|
64
|
+
@click="handlePasswordUpdate"
|
|
65
|
+
no-caps
|
|
66
|
+
:label="$t('auth.close')"
|
|
67
|
+
color="dark"
|
|
68
|
+
v-close-popup
|
|
69
|
+
/>
|
|
70
|
+
</slot>
|
|
69
71
|
</q-card-actions>
|
|
70
72
|
</q-card>
|
|
71
73
|
</template>
|
|
@@ -109,10 +111,10 @@ export default defineComponent({
|
|
|
109
111
|
);
|
|
110
112
|
|
|
111
113
|
const emailNotSend = computed(
|
|
112
|
-
() => !props.sendEmailSuccess && !props.passwordUpdated
|
|
114
|
+
() => !props.sendEmailSuccess && !props.passwordUpdated
|
|
113
115
|
);
|
|
114
116
|
const emailSended = computed(
|
|
115
|
-
() => props.sendEmailSuccess && !props.passwordUpdated
|
|
117
|
+
() => props.sendEmailSuccess && !props.passwordUpdated
|
|
116
118
|
);
|
|
117
119
|
|
|
118
120
|
const retypeEmail = () => {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
round
|
|
10
10
|
no-caps
|
|
11
11
|
color="dark"
|
|
12
|
+
v-if="!isOtp"
|
|
12
13
|
v-close-popup
|
|
13
14
|
/>
|
|
14
15
|
</div>
|
|
@@ -26,16 +27,20 @@
|
|
|
26
27
|
</div>
|
|
27
28
|
</q-card-section>
|
|
28
29
|
<q-card-actions class="row full-width justify-center q-mb-lg">
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
<div>
|
|
31
|
+
<slot>
|
|
32
|
+
<q-btn
|
|
33
|
+
class="bg-white submitBtn"
|
|
34
|
+
unelevated
|
|
35
|
+
outline
|
|
36
|
+
no-caps
|
|
37
|
+
:label="$t('auth.close')"
|
|
38
|
+
@click="passwordUpdated ? $emit('goToLoginPage') : ''"
|
|
39
|
+
color="primary"
|
|
40
|
+
v-close-popup
|
|
41
|
+
/>
|
|
42
|
+
</slot>
|
|
43
|
+
</div>
|
|
39
44
|
</q-card-actions>
|
|
40
45
|
</q-card>
|
|
41
46
|
</template>
|
|
@@ -53,6 +58,7 @@ export default defineComponent({
|
|
|
53
58
|
content: { type: String, required: true },
|
|
54
59
|
image: { type: String, required: true },
|
|
55
60
|
passwordUpdated: { type: Boolean },
|
|
61
|
+
isOtp: { type: Boolean, default: false, required: false },
|
|
56
62
|
},
|
|
57
63
|
emit: ["goToLoginPage"],
|
|
58
64
|
setup(props: any) {
|
|
@@ -125,7 +125,20 @@
|
|
|
125
125
|
:sendEmailSuccess="sendEmailSuccess"
|
|
126
126
|
:loading="loading"
|
|
127
127
|
@retypeEmail="retypeEmail"
|
|
128
|
-
|
|
128
|
+
>
|
|
129
|
+
<q-btn
|
|
130
|
+
v-if="(sendEmailSuccess && isOtp)"
|
|
131
|
+
class="q-mt-md full-width"
|
|
132
|
+
unelevated
|
|
133
|
+
style="background: #fff"
|
|
134
|
+
flat
|
|
135
|
+
@click="goToOtpValidation"
|
|
136
|
+
no-caps
|
|
137
|
+
:label="$t('auth.next')"
|
|
138
|
+
color="dark"
|
|
139
|
+
v-close-popup
|
|
140
|
+
/>
|
|
141
|
+
</sowell-message>
|
|
129
142
|
</q-dialog>
|
|
130
143
|
</div>
|
|
131
144
|
</q-page>
|
|
@@ -142,7 +155,7 @@
|
|
|
142
155
|
</template>
|
|
143
156
|
|
|
144
157
|
<script lang="ts">
|
|
145
|
-
import { defineComponent, reactive, onMounted,
|
|
158
|
+
import { defineComponent, reactive, onMounted, computed } from "vue";
|
|
146
159
|
import * as packageInfo from "app/package.json";
|
|
147
160
|
import SendEmail from "../dialogs/SendEmail.vue";
|
|
148
161
|
import SowellMessage from "../dialogs/Message.vue";
|
|
@@ -166,15 +179,20 @@ export default defineComponent({
|
|
|
166
179
|
sendEmailSuccess,
|
|
167
180
|
authState,
|
|
168
181
|
APP_SERVER_BASE_URL,
|
|
182
|
+
IS_OTP,
|
|
169
183
|
onSubmit,
|
|
170
184
|
retypeEmail,
|
|
171
185
|
isSubmitBtnDisabled,
|
|
186
|
+
goToOtpValidation,
|
|
172
187
|
icons,
|
|
173
188
|
} = useAuth();
|
|
174
189
|
|
|
175
190
|
const appVersion = reactive({
|
|
176
191
|
versionNumber: "",
|
|
177
192
|
});
|
|
193
|
+
|
|
194
|
+
const isOtp = computed(() => IS_OTP.toLowerCase() === "y")
|
|
195
|
+
|
|
178
196
|
const resetPassword = async (email: string) => {
|
|
179
197
|
const data = {
|
|
180
198
|
auth: {
|
|
@@ -185,6 +203,7 @@ export default defineComponent({
|
|
|
185
203
|
try {
|
|
186
204
|
forgottenPassword.value = false;
|
|
187
205
|
await authState.resetPassword(data).then(() => {
|
|
206
|
+
localStorage.setItem("emailResetPassword", email);
|
|
188
207
|
sendEmailSuccess.value = true;
|
|
189
208
|
isReset.value = true;
|
|
190
209
|
});
|
|
@@ -212,10 +231,12 @@ export default defineComponent({
|
|
|
212
231
|
sendEmailSuccess,
|
|
213
232
|
onSubmit,
|
|
214
233
|
resetPassword,
|
|
234
|
+
goToOtpValidation,
|
|
215
235
|
isSubmitBtnDisabled,
|
|
216
236
|
retypeEmail,
|
|
217
237
|
icons,
|
|
218
238
|
appVersion,
|
|
239
|
+
isOtp,
|
|
219
240
|
};
|
|
220
241
|
},
|
|
221
242
|
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-layout>
|
|
3
|
+
<q-page class="q-pa-md row justify-center">
|
|
4
|
+
<div>
|
|
5
|
+
<div class="q-mt-xl">
|
|
6
|
+
<img
|
|
7
|
+
src="../../assets/Sowell-logo.svg"
|
|
8
|
+
class="q-mb-sm q-mr-md"
|
|
9
|
+
style="width: 190px"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
<div
|
|
13
|
+
class="column justify-center full-height"
|
|
14
|
+
style="max-width: 550px; margin-top: -90px"
|
|
15
|
+
>
|
|
16
|
+
<div></div>
|
|
17
|
+
<div
|
|
18
|
+
class="text-subtitle1 q-mt-lg"
|
|
19
|
+
data-cy="validationOtp"
|
|
20
|
+
style="font-size: 34px; font-weight: 600; line-height: 34px"
|
|
21
|
+
>
|
|
22
|
+
{{ $t("auth.titleOtpValidation") }}
|
|
23
|
+
</div>
|
|
24
|
+
<div
|
|
25
|
+
class="text-subtitle1 q-mt-lg q-mb-lg"
|
|
26
|
+
style="font-size: 16px; font-weight: 400; line-height: 24px"
|
|
27
|
+
>
|
|
28
|
+
{{ $t("auth.subtitleOtpValidation") }}
|
|
29
|
+
</div>
|
|
30
|
+
<q-form class="column form" @submit="onSubmit">
|
|
31
|
+
<div>
|
|
32
|
+
<div class="q-mb-sm" style="font-size: 14px; font-weight: 600">
|
|
33
|
+
{{ $t("auth.validationCode") }}
|
|
34
|
+
</div>
|
|
35
|
+
<q-input
|
|
36
|
+
v-model="otp"
|
|
37
|
+
dense
|
|
38
|
+
:borderless="isReporterUi"
|
|
39
|
+
:outlined="!isReporterUi"
|
|
40
|
+
data-cy="validate-otp"
|
|
41
|
+
bg-color="white"
|
|
42
|
+
class="q-mb-md"
|
|
43
|
+
><template v-slot:prepend> </template>
|
|
44
|
+
</q-input>
|
|
45
|
+
</div>
|
|
46
|
+
<q-space />
|
|
47
|
+
<div class="row justify-between">
|
|
48
|
+
<q-btn
|
|
49
|
+
:label="$t('auth.next')"
|
|
50
|
+
color="primary"
|
|
51
|
+
:loading="loading"
|
|
52
|
+
type="submit"
|
|
53
|
+
no-caps
|
|
54
|
+
class="submitBtn"
|
|
55
|
+
data-cy="send-new-otp"
|
|
56
|
+
:disable="canSubmit"
|
|
57
|
+
@click="onSubmit"
|
|
58
|
+
/>
|
|
59
|
+
<q-btn
|
|
60
|
+
:label="$t('auth.login')"
|
|
61
|
+
text-color="primary"
|
|
62
|
+
type="submit"
|
|
63
|
+
no-caps
|
|
64
|
+
data-cy="login-page"
|
|
65
|
+
@click="() => router.push(LOCAL_LOGIN_ROUTE)"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</q-form>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</q-page>
|
|
72
|
+
</q-layout>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script lang="ts">
|
|
76
|
+
//@ts-ignore
|
|
77
|
+
import { defineComponent, ref, computed } from "vue";
|
|
78
|
+
import { $router, $prompts, $api } from "../../injects";
|
|
79
|
+
import { useQuasar } from "quasar";
|
|
80
|
+
import { useI18n } from "vue-i18n";
|
|
81
|
+
import jwt from "jwt-decode";
|
|
82
|
+
|
|
83
|
+
import {
|
|
84
|
+
ionCloseOutline,
|
|
85
|
+
ionAlertCircleOutline,
|
|
86
|
+
} from "@quasar/extras/ionicons-v5";
|
|
87
|
+
|
|
88
|
+
interface DecodedToken {
|
|
89
|
+
uid: string;
|
|
90
|
+
cid: string;
|
|
91
|
+
exp: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default defineComponent({
|
|
95
|
+
name: "ResetPassword",
|
|
96
|
+
components: {},
|
|
97
|
+
setup() {
|
|
98
|
+
const { t } = useI18n();
|
|
99
|
+
const router = $router();
|
|
100
|
+
const $q = useQuasar();
|
|
101
|
+
const { post } = $api();
|
|
102
|
+
const {
|
|
103
|
+
LOCAL_LOGIN_ROUTE,
|
|
104
|
+
AUTH_SERVER_VERIFY_OTP,
|
|
105
|
+
LOCAL_UPDATE_PASSWORD_ROUTE,
|
|
106
|
+
UI_PAGE,
|
|
107
|
+
} = $prompts();
|
|
108
|
+
|
|
109
|
+
const otp = ref("");
|
|
110
|
+
const loading = ref(false);
|
|
111
|
+
|
|
112
|
+
const isReporterUi = computed(() => UI_PAGE === "reporter");
|
|
113
|
+
|
|
114
|
+
const onSubmit = () => {
|
|
115
|
+
if (canSubmit && localStorage.getItem("emailResetPassword")) {
|
|
116
|
+
const data = {
|
|
117
|
+
auth: {
|
|
118
|
+
email: localStorage.getItem("emailResetPassword"),
|
|
119
|
+
password: otp.value,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
post(AUTH_SERVER_VERIFY_OTP, data)
|
|
123
|
+
.then((response) => {
|
|
124
|
+
if (response.data.auth) {
|
|
125
|
+
let decoded = jwt(response.data.auth) as DecodedToken;
|
|
126
|
+
if (decoded) {
|
|
127
|
+
localStorage.setItem(
|
|
128
|
+
"userInfos",
|
|
129
|
+
JSON.stringify({
|
|
130
|
+
uid: decoded.uid,
|
|
131
|
+
token: response.data.auth,
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
router.push(LOCAL_UPDATE_PASSWORD_ROUTE);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
console.log("error", error);
|
|
140
|
+
$q.notify({
|
|
141
|
+
message: t("auth.passwordRecoveryFailed"),
|
|
142
|
+
color: "negative",
|
|
143
|
+
icon: ionAlertCircleOutline,
|
|
144
|
+
position: "top",
|
|
145
|
+
actions: [
|
|
146
|
+
{
|
|
147
|
+
color: "white",
|
|
148
|
+
icon: ionCloseOutline,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const canSubmit = computed(() => {
|
|
157
|
+
return !otp.value.length;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
otp,
|
|
162
|
+
loading,
|
|
163
|
+
router,
|
|
164
|
+
LOCAL_LOGIN_ROUTE,
|
|
165
|
+
isReporterUi,
|
|
166
|
+
onSubmit,
|
|
167
|
+
canSubmit,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<style scoped>
|
|
174
|
+
.submitBtn {
|
|
175
|
+
width: 254px;
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
<div class="row window-height">
|
|
4
4
|
<div class="col column content-center bgWhite">
|
|
5
5
|
<div class="q-mt-xl">
|
|
6
|
-
<img
|
|
7
|
-
src="../../assets/Sowell-logo.svg"
|
|
8
|
-
style="width: 210px"
|
|
9
|
-
/>
|
|
6
|
+
<img src="../../assets/Sowell-logo.svg" style="width: 210px" />
|
|
10
7
|
</div>
|
|
11
|
-
<div class="column col content-center justify-center center"
|
|
8
|
+
<div class="column col content-center justify-center center">
|
|
12
9
|
<div class="q-pl-md q-pr-md" style="max-width: 500px">
|
|
13
10
|
<h4 class="title">{{ WELCOME_TITLE }}</h4>
|
|
14
11
|
<div class="subtitle">{{ WELCOME_SUBTITLE }}</div>
|
|
@@ -22,7 +19,7 @@
|
|
|
22
19
|
type="email"
|
|
23
20
|
:rules="validations['email']"
|
|
24
21
|
lazy-rules
|
|
25
|
-
|
|
22
|
+
data-cy="email"
|
|
26
23
|
dense
|
|
27
24
|
outlined
|
|
28
25
|
input-class="text-subtitle2 text-weight-medium"
|
|
@@ -66,7 +63,7 @@
|
|
|
66
63
|
/>
|
|
67
64
|
<div
|
|
68
65
|
class="cursor-pointer text-primary text-subtitle2"
|
|
69
|
-
|
|
66
|
+
data-cy="forgot-password"
|
|
70
67
|
@click="forgottenPassword = true"
|
|
71
68
|
>
|
|
72
69
|
{{ $t("auth.forgottenPassword") }}
|
|
@@ -78,15 +75,19 @@
|
|
|
78
75
|
</div>
|
|
79
76
|
<div
|
|
80
77
|
class="column col justify-center content-center mobile"
|
|
81
|
-
:style="
|
|
82
|
-
:class="!
|
|
78
|
+
:style="IS_MANAGER ? 'background: #eef8fd' : ''"
|
|
79
|
+
:class="!IS_MANAGER ? 'bgBlue' : ''"
|
|
83
80
|
>
|
|
84
81
|
<img
|
|
85
|
-
v-if="
|
|
82
|
+
v-if="IS_MANAGER"
|
|
86
83
|
src="../../assets/illustration-login.svg"
|
|
87
84
|
class="q-pl-md q-pr-md logoWhite"
|
|
88
85
|
/>
|
|
89
|
-
<img
|
|
86
|
+
<img
|
|
87
|
+
v-else
|
|
88
|
+
src="../../assets/SowellWhite.svg"
|
|
89
|
+
class="q-pl-md q-pr-md logoWhite"
|
|
90
|
+
/>
|
|
90
91
|
</div>
|
|
91
92
|
</div>
|
|
92
93
|
<div>
|
|
@@ -105,7 +106,20 @@
|
|
|
105
106
|
:title="infoDialog.title"
|
|
106
107
|
:content="infoDialog.content"
|
|
107
108
|
:image="infoDialog.image"
|
|
108
|
-
|
|
109
|
+
:isOtp="true"
|
|
110
|
+
>
|
|
111
|
+
<q-btn
|
|
112
|
+
v-if="requestedOtp"
|
|
113
|
+
class="bg-white submitBtn"
|
|
114
|
+
unelevated
|
|
115
|
+
outline
|
|
116
|
+
no-caps
|
|
117
|
+
:label="$t('auth.next')"
|
|
118
|
+
color="primary"
|
|
119
|
+
@click="goToOtpValidation"
|
|
120
|
+
v-close-popup
|
|
121
|
+
/>
|
|
122
|
+
</sw-info-dialog>
|
|
109
123
|
</q-dialog>
|
|
110
124
|
</div>
|
|
111
125
|
</q-layout>
|
|
@@ -144,14 +158,18 @@ export default defineComponent({
|
|
|
144
158
|
WELCOME_TITLE,
|
|
145
159
|
WELCOME_SUBTITLE,
|
|
146
160
|
X_REQUESTED_ROLES,
|
|
161
|
+
IS_OTP,
|
|
147
162
|
APP_SERVER_BASE_URL,
|
|
163
|
+
goToOtpValidation,
|
|
148
164
|
onSubmit,
|
|
149
165
|
retypeEmail,
|
|
150
166
|
isSubmitBtnDisabled,
|
|
151
167
|
icons,
|
|
152
168
|
} = useAuth();
|
|
153
169
|
|
|
170
|
+
const IS_MANAGER = ["manager", "clean_manager"].includes(X_REQUESTED_ROLES);
|
|
154
171
|
const isNotification = ref(false);
|
|
172
|
+
const requestedOtp = ref(false);
|
|
155
173
|
const infoDialog = ref({
|
|
156
174
|
title: "",
|
|
157
175
|
content: "",
|
|
@@ -165,33 +183,40 @@ export default defineComponent({
|
|
|
165
183
|
isEmail(email.value) &&
|
|
166
184
|
password.value &&
|
|
167
185
|
password.value.length > 5
|
|
168
|
-
)
|
|
186
|
+
)
|
|
169
187
|
);
|
|
170
|
-
const isManager = computed(() => X_REQUESTED_ROLES === "clean_manager");
|
|
171
188
|
const resetPassword = async (email: string) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
await authState.resetPassword(data);
|
|
180
|
-
forgottenPassword.value = false;
|
|
181
|
-
infoDialog.value = {
|
|
182
|
-
title: t("auth.mailVerificationTitle"),
|
|
183
|
-
content: t("auth.mailVerificationSubtitle"),
|
|
184
|
-
image: "emailVerification",
|
|
189
|
+
if (email) {
|
|
190
|
+
const data = {
|
|
191
|
+
auth: {
|
|
192
|
+
email,
|
|
193
|
+
base: APP_SERVER_BASE_URL,
|
|
194
|
+
},
|
|
185
195
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
try {
|
|
197
|
+
await authState.resetPassword(data).then(() => {
|
|
198
|
+
localStorage.setItem("emailResetPassword", email);
|
|
199
|
+
infoDialog.value = {
|
|
200
|
+
title: t("auth.mailVerificationTitle"),
|
|
201
|
+
content:
|
|
202
|
+
IS_OTP.toLowerCase() === "y"
|
|
203
|
+
? t("auth.otpValidationCode")
|
|
204
|
+
: t("auth.mailVerificationSubtitle"),
|
|
205
|
+
image: "emailVerification",
|
|
206
|
+
};
|
|
207
|
+
forgottenPassword.value = false;
|
|
208
|
+
isNotification.value = true;
|
|
209
|
+
requestedOtp.value = true;
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
requestedOtp.value = false;
|
|
213
|
+
infoDialog.value = {
|
|
214
|
+
title: t("auth.userNotFoundTitle"),
|
|
215
|
+
content: t("auth.userNotFoundSubtitle"),
|
|
216
|
+
image: "userNotFound",
|
|
217
|
+
};
|
|
218
|
+
isNotification.value = true;
|
|
219
|
+
}
|
|
195
220
|
}
|
|
196
221
|
};
|
|
197
222
|
|
|
@@ -206,10 +231,12 @@ export default defineComponent({
|
|
|
206
231
|
forgottenPassword,
|
|
207
232
|
emailResetPassword,
|
|
208
233
|
isNotification,
|
|
209
|
-
|
|
234
|
+
IS_MANAGER,
|
|
210
235
|
WELCOME_SUBTITLE,
|
|
211
236
|
WELCOME_TITLE,
|
|
212
237
|
isSubmitBtnDisabled,
|
|
238
|
+
requestedOtp,
|
|
239
|
+
goToOtpValidation,
|
|
213
240
|
resetPassword,
|
|
214
241
|
retypeEmail,
|
|
215
242
|
onSubmit,
|
|
@@ -220,7 +247,6 @@ export default defineComponent({
|
|
|
220
247
|
</script>
|
|
221
248
|
|
|
222
249
|
<style scoped>
|
|
223
|
-
|
|
224
250
|
.center {
|
|
225
251
|
margin-top: -115px;
|
|
226
252
|
}
|
package/src/composables/auth.ts
CHANGED
|
@@ -29,6 +29,8 @@ export function useAuth() {
|
|
|
29
29
|
X_REQUESTED_ROLES,
|
|
30
30
|
WELCOME_TITLE,
|
|
31
31
|
WELCOME_SUBTITLE,
|
|
32
|
+
IS_OTP,
|
|
33
|
+
LOCAL_OTP_ROUTE,
|
|
32
34
|
} = $prompts();
|
|
33
35
|
const { push, replace } = $router();
|
|
34
36
|
|
|
@@ -87,19 +89,18 @@ export function useAuth() {
|
|
|
87
89
|
if (requestedRoles) options.headers["x-requested-roles"] = requestedRoles;
|
|
88
90
|
|
|
89
91
|
try {
|
|
90
|
-
await authState.signin(data, options)
|
|
92
|
+
await authState.signin(data, options);
|
|
91
93
|
loading.value = false;
|
|
92
94
|
// @ts-ignore
|
|
93
95
|
if (!window.Cypress) {
|
|
94
96
|
// FIXME: encrypt password
|
|
95
|
-
localStorage.setItem("credentials", JSON.stringify(data.auth))
|
|
97
|
+
localStorage.setItem("credentials", JSON.stringify(data.auth));
|
|
96
98
|
}
|
|
97
99
|
push(LOCAL_SUCCESS_REDIRECTION_ROUTE);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error("onSubmit", err);
|
|
100
102
|
loading.value = false;
|
|
101
103
|
}
|
|
102
|
-
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
const isSubmitBtnDisabled = () => {
|
|
@@ -120,6 +121,10 @@ export function useAuth() {
|
|
|
120
121
|
}
|
|
121
122
|
};
|
|
122
123
|
|
|
124
|
+
const goToOtpValidation = () => {
|
|
125
|
+
push(LOCAL_OTP_ROUTE);
|
|
126
|
+
};
|
|
127
|
+
|
|
123
128
|
onMounted(() => {
|
|
124
129
|
if (authState.raw) {
|
|
125
130
|
replace(LOCAL_SUCCESS_REDIRECTION_ROUTE);
|
|
@@ -146,6 +151,9 @@ export function useAuth() {
|
|
|
146
151
|
WELCOME_TITLE,
|
|
147
152
|
WELCOME_SUBTITLE,
|
|
148
153
|
X_REQUESTED_ROLES,
|
|
154
|
+
IS_OTP,
|
|
155
|
+
LOCAL_OTP_ROUTE,
|
|
156
|
+
goToOtpValidation,
|
|
149
157
|
onSubmit,
|
|
150
158
|
retypeEmail,
|
|
151
159
|
isSubmitBtnDisabled,
|
|
@@ -27,6 +27,7 @@ export function useResetPassword() {
|
|
|
27
27
|
const passwordUpdated = ref(false);
|
|
28
28
|
const showPassword = ref(false);
|
|
29
29
|
const token = ref("");
|
|
30
|
+
const userInfos = JSON.parse(localStorage.getItem("userInfos") || "{}");
|
|
30
31
|
|
|
31
32
|
const { patch, post } = $api();
|
|
32
33
|
const router = $router();
|
|
@@ -34,6 +35,7 @@ export function useResetPassword() {
|
|
|
34
35
|
AUTH_SERVER_SIGNING_ROUTE,
|
|
35
36
|
AUTH_SERVER_UPDATE_PASSWORD_ROUTE,
|
|
36
37
|
LOCAL_LOGIN_ROUTE,
|
|
38
|
+
IS_OTP,
|
|
37
39
|
} = $prompts();
|
|
38
40
|
const $route = router.currentRoute.value;
|
|
39
41
|
|
|
@@ -42,8 +44,12 @@ export function useResetPassword() {
|
|
|
42
44
|
return jwt(token.value) as DecodedToken;
|
|
43
45
|
}) as Ref<DecodedToken | null>;
|
|
44
46
|
|
|
47
|
+
const isOtp = computed(() => IS_OTP.toLowerCase() === "y");
|
|
45
48
|
const canSubmit = computed(() => {
|
|
46
|
-
return
|
|
49
|
+
return (
|
|
50
|
+
(decodedToken.value !== null || userInfos?.uid) &&
|
|
51
|
+
password.value.length >= 6
|
|
52
|
+
);
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
const validations = {
|
|
@@ -87,10 +93,12 @@ export function useResetPassword() {
|
|
|
87
93
|
};
|
|
88
94
|
|
|
89
95
|
const onSubmit = () => {
|
|
96
|
+
let idUser =
|
|
97
|
+
isOtp && userInfos?.uid ? userInfos?.uid : decodedToken?.value?.sub;
|
|
90
98
|
const updatePasswordPayload = {
|
|
91
99
|
data: {
|
|
92
100
|
type: "users",
|
|
93
|
-
id:
|
|
101
|
+
id: idUser,
|
|
94
102
|
attributes: {
|
|
95
103
|
password: password.value,
|
|
96
104
|
},
|
|
@@ -98,7 +106,7 @@ export function useResetPassword() {
|
|
|
98
106
|
};
|
|
99
107
|
const updatePasswordHeaders = {
|
|
100
108
|
headers: {
|
|
101
|
-
Authorization: `Bearer ${token.value}`,
|
|
109
|
+
Authorization: `Bearer ${isOtp ? userInfos?.token : token.value}`,
|
|
102
110
|
},
|
|
103
111
|
};
|
|
104
112
|
loading.value = true;
|
|
@@ -106,14 +114,16 @@ export function useResetPassword() {
|
|
|
106
114
|
patch(
|
|
107
115
|
AUTH_SERVER_UPDATE_PASSWORD_ROUTE.replace(
|
|
108
116
|
":userId",
|
|
109
|
-
String(decodedToken.value.sub)
|
|
117
|
+
String(isOtp ? userInfos?.uid : decodedToken.value.sub)
|
|
110
118
|
),
|
|
111
119
|
updatePasswordPayload,
|
|
112
|
-
updatePasswordHeaders
|
|
120
|
+
updatePasswordHeaders
|
|
113
121
|
)
|
|
114
122
|
.then(() => {
|
|
115
123
|
loading.value = false;
|
|
116
124
|
passwordUpdated.value = true;
|
|
125
|
+
localStorage.removeItem("emailResetPassword");
|
|
126
|
+
localStorage.removeItem("userInfos");
|
|
117
127
|
})
|
|
118
128
|
.catch(() => {
|
|
119
129
|
loading.value = false;
|
|
@@ -137,56 +147,13 @@ export function useResetPassword() {
|
|
|
137
147
|
};
|
|
138
148
|
|
|
139
149
|
onMounted(() => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!decoded) {
|
|
144
|
-
return $q.notify({
|
|
145
|
-
message: t("auth.passwordRecoveryInvalidLink"),
|
|
146
|
-
color: "negative",
|
|
147
|
-
position: "top",
|
|
148
|
-
icon: ionAlertCircleOutline,
|
|
149
|
-
actions: [
|
|
150
|
-
{
|
|
151
|
-
color: "white",
|
|
152
|
-
icon: ionCloseOutline,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const tmpAuthPayload = {
|
|
159
|
-
auth: {
|
|
160
|
-
email: email,
|
|
161
|
-
token: tmpToken,
|
|
162
|
-
},
|
|
163
|
-
};
|
|
150
|
+
if (IS_OTP.toLowerCase() === "n") {
|
|
151
|
+
let key = $route.query.key as string;
|
|
152
|
+
const { decoded, email, tmpToken } = decodeKey(key);
|
|
164
153
|
|
|
165
|
-
|
|
166
|
-
.then((response) => {
|
|
167
|
-
token.value = response.data.jwt;
|
|
168
|
-
if (!token.value) throw "Error reading the temporary token";
|
|
169
|
-
})
|
|
170
|
-
.catch((error) => {
|
|
171
|
-
if (error.response) {
|
|
172
|
-
// Request made and server responded
|
|
173
|
-
if (error.response.status === 404) {
|
|
174
|
-
return $q.notify({
|
|
175
|
-
message: t("auth.passwordRecoveryExpiredLink"),
|
|
176
|
-
color: "negative",
|
|
177
|
-
icon: ionAlertCircleOutline,
|
|
178
|
-
position: "top",
|
|
179
|
-
actions: [
|
|
180
|
-
{
|
|
181
|
-
color: "white",
|
|
182
|
-
icon: ionCloseOutline,
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
154
|
+
if (!decoded) {
|
|
188
155
|
return $q.notify({
|
|
189
|
-
message: t("auth.
|
|
156
|
+
message: t("auth.passwordRecoveryInvalidLink"),
|
|
190
157
|
color: "negative",
|
|
191
158
|
position: "top",
|
|
192
159
|
icon: ionAlertCircleOutline,
|
|
@@ -197,7 +164,52 @@ export function useResetPassword() {
|
|
|
197
164
|
},
|
|
198
165
|
],
|
|
199
166
|
});
|
|
200
|
-
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const tmpAuthPayload = {
|
|
170
|
+
auth: {
|
|
171
|
+
email: email,
|
|
172
|
+
token: tmpToken,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
post(AUTH_SERVER_SIGNING_ROUTE, tmpAuthPayload)
|
|
177
|
+
.then((response) => {
|
|
178
|
+
token.value = response.data.jwt;
|
|
179
|
+
if (!token.value) throw "Error reading the temporary token";
|
|
180
|
+
})
|
|
181
|
+
.catch((error) => {
|
|
182
|
+
if (error.response) {
|
|
183
|
+
// Request made and server responded
|
|
184
|
+
if (error.response.status === 404) {
|
|
185
|
+
return $q.notify({
|
|
186
|
+
message: t("auth.passwordRecoveryExpiredLink"),
|
|
187
|
+
color: "negative",
|
|
188
|
+
icon: ionAlertCircleOutline,
|
|
189
|
+
position: "top",
|
|
190
|
+
actions: [
|
|
191
|
+
{
|
|
192
|
+
color: "white",
|
|
193
|
+
icon: ionCloseOutline,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return $q.notify({
|
|
200
|
+
message: t("auth.passwordRecoveryFailed"),
|
|
201
|
+
color: "negative",
|
|
202
|
+
position: "top",
|
|
203
|
+
icon: ionAlertCircleOutline,
|
|
204
|
+
actions: [
|
|
205
|
+
{
|
|
206
|
+
color: "white",
|
|
207
|
+
icon: ionCloseOutline,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
201
213
|
});
|
|
202
214
|
|
|
203
215
|
return {
|
package/src/injects/prompts.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface PromptsInterface {
|
|
|
18
18
|
X_REQUESTED_ROLES: string;
|
|
19
19
|
WELCOME_TITLE: string;
|
|
20
20
|
WELCOME_SUBTITLE: string;
|
|
21
|
+
IS_OTP: string;
|
|
22
|
+
LOCAL_OTP_ROUTE: string;
|
|
23
|
+
AUTH_SERVER_VERIFY_OTP: string;
|
|
24
|
+
UI_PAGE: string;
|
|
21
25
|
}
|
|
22
26
|
export const $prompts = (): PromptsInterface => {
|
|
23
27
|
const $prompts = inject<Record<"@sowell/auth", PromptsInterface>>("$prompts");
|
package/src/prompts.js
CHANGED
|
@@ -148,5 +148,26 @@ module.exports = function () {
|
|
|
148
148
|
message: "Subtitle on login page",
|
|
149
149
|
default: "",
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: "IS_OTP",
|
|
153
|
+
type: "input",
|
|
154
|
+
required: true,
|
|
155
|
+
message: "Use OTP system reset password (Y/N or y/n)",
|
|
156
|
+
default: "N",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "LOCAL_OTP_ROUTE",
|
|
160
|
+
type: "input",
|
|
161
|
+
required: true,
|
|
162
|
+
message: "Route to validation otp (page)",
|
|
163
|
+
default: "/otp-validation",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "AUTH_SERVER_VERIFY_OTP",
|
|
167
|
+
type: "input",
|
|
168
|
+
required: true,
|
|
169
|
+
message: "API endpoint to send OTP (without host)",
|
|
170
|
+
default: "/sign-in-otp",
|
|
171
|
+
},
|
|
151
172
|
];
|
|
152
173
|
};
|
package/src/routes/index.ts
CHANGED
|
@@ -8,11 +8,12 @@ const {
|
|
|
8
8
|
LOCAL_UPDATE_PASSWORD_ROUTE,
|
|
9
9
|
X_REQUESTED_ROLES,
|
|
10
10
|
UI_PAGE,
|
|
11
|
+
LOCAL_OTP_ROUTE,
|
|
11
12
|
},
|
|
12
13
|
} = prompts;
|
|
13
14
|
|
|
14
15
|
// FIXME: Utiliser le UI_PAGE pour choisir les page a utiliser pour la version web ou mobile (auth et reset password)
|
|
15
|
-
const webAuthRoles = ["superadmin", "clean_manager"];
|
|
16
|
+
const webAuthRoles = ["superadmin", "clean_manager", "manager"];
|
|
16
17
|
|
|
17
18
|
let mobileAuthPage = "";
|
|
18
19
|
|
|
@@ -54,6 +55,13 @@ const routes: RouteRecordRaw[] = [
|
|
|
54
55
|
}`
|
|
55
56
|
),
|
|
56
57
|
},
|
|
58
|
+
{
|
|
59
|
+
path: LOCAL_OTP_ROUTE,
|
|
60
|
+
name: "otp_validation",
|
|
61
|
+
component: () =>
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
import("@sowell/auth/pages/OtpValidation.vue"),
|
|
64
|
+
},
|
|
57
65
|
];
|
|
58
66
|
|
|
59
67
|
export default routes;
|