@sowell/quasar-app-extension-auth 0.3.21 → 0.3.23
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 +2 -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 +59 -33
- package/src/composables/auth.ts +51 -11
- package/src/composables/resetPassword.ts +66 -54
- package/src/injects/prompts.ts +5 -0
- package/src/injects/states.ts +12 -3
- package/src/prompts.js +27 -0
- package/src/routes/index.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sowell/quasar-app-extension-auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.23",
|
|
4
4
|
"description": "A Quasar app extension to manage Authentication",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"repository": "https://github.com/sowell-ai/quasar.extension.auth",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"test": "test"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"@capacitor/core": "^4.6.2",
|
|
41
42
|
"@quasar/extras": "^1.13.4",
|
|
42
43
|
"axios": "^0.21.1",
|
|
43
44
|
"buffer": "^6.0.3",
|
|
@@ -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") }}
|
|
@@ -86,7 +83,11 @@
|
|
|
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,7 +158,9 @@ 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,
|
|
@@ -153,6 +169,7 @@ export default defineComponent({
|
|
|
153
169
|
|
|
154
170
|
const IS_MANAGER = ["manager", "clean_manager"].includes(X_REQUESTED_ROLES);
|
|
155
171
|
const isNotification = ref(false);
|
|
172
|
+
const requestedOtp = ref(false);
|
|
156
173
|
const infoDialog = ref({
|
|
157
174
|
title: "",
|
|
158
175
|
content: "",
|
|
@@ -166,32 +183,40 @@ export default defineComponent({
|
|
|
166
183
|
isEmail(email.value) &&
|
|
167
184
|
password.value &&
|
|
168
185
|
password.value.length > 5
|
|
169
|
-
)
|
|
186
|
+
)
|
|
170
187
|
);
|
|
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
|
|
|
@@ -210,6 +235,8 @@ export default defineComponent({
|
|
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, onMounted } from "vue";
|
|
1
|
+
import { ref, onMounted, inject } from "vue";
|
|
2
2
|
import isEmail from "validator/es/lib/isEmail";
|
|
3
3
|
import { useI18n } from "vue-i18n";
|
|
4
4
|
import {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "@quasar/extras/ionicons-v5";
|
|
12
12
|
import { Platform } from "quasar";
|
|
13
13
|
|
|
14
|
-
import { $prompts, $router, useAuthState } from "../injects";
|
|
14
|
+
import { $prompts, $router, SignInPayload, useAuthState } from "../injects";
|
|
15
15
|
|
|
16
16
|
interface HeadersInterface {
|
|
17
17
|
headers: {
|
|
@@ -21,6 +21,13 @@ interface HeadersInterface {
|
|
|
21
21
|
|
|
22
22
|
export function useAuth() {
|
|
23
23
|
const authState = useAuthState();
|
|
24
|
+
const device = inject("Device")
|
|
25
|
+
let deviceInfos = {
|
|
26
|
+
cleanManufacturer: "",
|
|
27
|
+
cleanModel: "",
|
|
28
|
+
cleanPlatform: "",
|
|
29
|
+
cleanVersion: ""
|
|
30
|
+
}
|
|
24
31
|
|
|
25
32
|
const { t } = useI18n();
|
|
26
33
|
const {
|
|
@@ -29,6 +36,9 @@ export function useAuth() {
|
|
|
29
36
|
X_REQUESTED_ROLES,
|
|
30
37
|
WELCOME_TITLE,
|
|
31
38
|
WELCOME_SUBTITLE,
|
|
39
|
+
IS_OTP,
|
|
40
|
+
LOCAL_OTP_ROUTE,
|
|
41
|
+
APP_VERSION
|
|
32
42
|
} = $prompts();
|
|
33
43
|
const { push, replace } = $router();
|
|
34
44
|
|
|
@@ -69,38 +79,60 @@ export function useAuth() {
|
|
|
69
79
|
forgottenPassword.value = true;
|
|
70
80
|
};
|
|
71
81
|
|
|
82
|
+
const deviceInfo = async () => {
|
|
83
|
+
if (device) {
|
|
84
|
+
const info = await device.getInfo();
|
|
85
|
+
deviceInfos = {
|
|
86
|
+
cleanManufacturer: info.manufacturer,
|
|
87
|
+
cleanModel: info.model,
|
|
88
|
+
cleanPlatform: info.platform,
|
|
89
|
+
cleanVersion: info.osVersion
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
72
94
|
const onSubmit = async () => {
|
|
73
95
|
loading.value = true;
|
|
74
|
-
const data = {
|
|
96
|
+
const data: SignInPayload = {
|
|
75
97
|
auth: {
|
|
76
98
|
email: email.value,
|
|
77
99
|
password: password.value,
|
|
78
100
|
},
|
|
79
101
|
};
|
|
102
|
+
if (device) {
|
|
103
|
+
data.device = {
|
|
104
|
+
cleanManufacturer: deviceInfos.cleanManufacturer,
|
|
105
|
+
cleanModel: deviceInfos.cleanModel,
|
|
106
|
+
cleanPlatform: deviceInfos.cleanPlatform,
|
|
107
|
+
cleanVersion: deviceInfos.cleanVersion,
|
|
108
|
+
},
|
|
109
|
+
data.app = {
|
|
110
|
+
cleanVersionNumber: APP_VERSION
|
|
111
|
+
}
|
|
112
|
+
};
|
|
80
113
|
const options: HeadersInterface = {
|
|
81
114
|
headers: {},
|
|
82
115
|
};
|
|
83
116
|
// @ts-ignore
|
|
84
117
|
let requestedRoles = window.Cypress
|
|
85
|
-
?
|
|
118
|
+
? // @ts-ignore
|
|
119
|
+
window.Cypress.requestedRoles
|
|
86
120
|
: X_REQUESTED_ROLES;
|
|
87
121
|
if (requestedRoles) options.headers["x-requested-roles"] = requestedRoles;
|
|
88
122
|
|
|
89
123
|
try {
|
|
90
|
-
await authState.signin(data, options)
|
|
124
|
+
await authState.signin(data, options);
|
|
91
125
|
loading.value = false;
|
|
92
126
|
// @ts-ignore
|
|
93
127
|
if (!window.Cypress) {
|
|
94
128
|
// FIXME: encrypt password
|
|
95
|
-
localStorage.setItem("credentials", JSON.stringify(data.auth))
|
|
129
|
+
localStorage.setItem("credentials", JSON.stringify(data.auth));
|
|
96
130
|
}
|
|
97
131
|
push(LOCAL_SUCCESS_REDIRECTION_ROUTE);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
console.error("onSubmit", err)
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error("onSubmit", err);
|
|
101
134
|
loading.value = false;
|
|
102
135
|
}
|
|
103
|
-
|
|
104
136
|
};
|
|
105
137
|
|
|
106
138
|
const isSubmitBtnDisabled = () => {
|
|
@@ -121,7 +153,12 @@ export function useAuth() {
|
|
|
121
153
|
}
|
|
122
154
|
};
|
|
123
155
|
|
|
124
|
-
|
|
156
|
+
const goToOtpValidation = () => {
|
|
157
|
+
push(LOCAL_OTP_ROUTE);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
onMounted(async () => {
|
|
161
|
+
await deviceInfo()
|
|
125
162
|
if (authState.raw) {
|
|
126
163
|
replace(LOCAL_SUCCESS_REDIRECTION_ROUTE);
|
|
127
164
|
}
|
|
@@ -147,6 +184,9 @@ export function useAuth() {
|
|
|
147
184
|
WELCOME_TITLE,
|
|
148
185
|
WELCOME_SUBTITLE,
|
|
149
186
|
X_REQUESTED_ROLES,
|
|
187
|
+
IS_OTP,
|
|
188
|
+
LOCAL_OTP_ROUTE,
|
|
189
|
+
goToOtpValidation,
|
|
150
190
|
onSubmit,
|
|
151
191
|
retypeEmail,
|
|
152
192
|
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,11 @@ 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;
|
|
25
|
+
APP_VERSION: string;
|
|
21
26
|
}
|
|
22
27
|
export const $prompts = (): PromptsInterface => {
|
|
23
28
|
const $prompts = inject<Record<"@sowell/auth", PromptsInterface>>("$prompts");
|
package/src/injects/states.ts
CHANGED
|
@@ -23,9 +23,18 @@ export interface RequestOptions {
|
|
|
23
23
|
|
|
24
24
|
export interface SignInPayload {
|
|
25
25
|
auth: {
|
|
26
|
-
email: string
|
|
27
|
-
password: string
|
|
28
|
-
}
|
|
26
|
+
email: string
|
|
27
|
+
password: string
|
|
28
|
+
}
|
|
29
|
+
device?: {
|
|
30
|
+
cleanManufacturer: string
|
|
31
|
+
cleanModel: string
|
|
32
|
+
cleanPlatform: string,
|
|
33
|
+
cleanVersion: string
|
|
34
|
+
}
|
|
35
|
+
app?: {
|
|
36
|
+
cleanVersionNumber: string
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
export interface ResetPasswordPayload {
|
package/src/prompts.js
CHANGED
|
@@ -148,5 +148,32 @@ 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
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "APP_VERSION",
|
|
174
|
+
required: true,
|
|
175
|
+
message: "Version de l'application",
|
|
176
|
+
default: "",
|
|
177
|
+
},
|
|
151
178
|
];
|
|
152
179
|
};
|
package/src/routes/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ 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
|
|
|
@@ -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;
|