@sowell/quasar-app-extension-auth 1.8.6 → 1.9.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sowell/quasar-app-extension-auth",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
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,7 +38,7 @@
38
38
  }}<strong>{{ messageSubtitleParts.bold }}</strong
39
39
  >{{ messageSubtitleParts.after }}
40
40
  </div>
41
- <div v-if="isForbidden" class="forbidden-sso-section">
41
+ <div v-if="isForbidden && USE_SSO" class="forbidden-sso-section">
42
42
  <div class="forbidden-sso-label">{{ $t("auth.continueWith") }}</div>
43
43
  <q-btn
44
44
  data-testid="forbidden-sso-btn"
@@ -96,6 +96,7 @@
96
96
  <script lang="ts">
97
97
  import { defineComponent, computed } from "vue"
98
98
  import { useI18n } from "vue-i18n"
99
+ import prompts from "app/quasar.extensions.json"
99
100
 
100
101
  const FORBIDDEN_MESSAGE_BOLD_KEYWORD = "Microsoft"
101
102
 
@@ -120,6 +121,9 @@ export default defineComponent({
120
121
  emit: ["retypeEmail", "goToLoginPage", "close", "msalAuth"],
121
122
  setup(props: any, { emit }: any) {
122
123
  const { t } = useI18n()
124
+ const {
125
+ "@sowell/auth": { USE_SSO }
126
+ } = prompts
123
127
 
124
128
  const isForbidden = computed(() => props.sendEmailCode === 403)
125
129
 
@@ -191,6 +195,7 @@ export default defineComponent({
191
195
  emailNotSend,
192
196
  emailSended,
193
197
  isForbidden,
198
+ USE_SSO,
194
199
  handlePasswordUpdate,
195
200
  retypeEmail,
196
201
  onMsalAuth
@@ -1,5 +1,67 @@
1
1
  <template>
2
2
  <div
3
+ v-if="restricted"
4
+ class="column items-center full-width restricted-page"
5
+ data-testid="ask-password-change-restricted"
6
+ >
7
+ <div class="background-logo-container">
8
+ <img
9
+ src="../../assets/Background-logo.svg"
10
+ class="background-logo"
11
+ alt="Background logo"
12
+ />
13
+ </div>
14
+ <div class="row justify-between items-center full-width q-px-lg q-pt-lg top-bar">
15
+ <div
16
+ class="row items-center back-link"
17
+ data-testid="restricted-back-btn"
18
+ data-cy="close_send_email"
19
+ @click="emit('close')"
20
+ >
21
+ <q-icon name="arrow_back" size="16px" />
22
+ <span class="q-ml-xs">{{ $t("auth.back") }}</span>
23
+ </div>
24
+ <img src="../../assets/sowell-min.svg" style="width: auto; height: 33px" />
25
+ </div>
26
+
27
+ <div class="full-width restricted-card">
28
+ <div class="column items-center">
29
+ <img src="../../assets/restricted.svg" class="restricted-badge" alt="" />
30
+ <div class="restricted-title text-center q-mt-md">
31
+ {{ $t("auth.restrictedTitle") }}
32
+ </div>
33
+ <div
34
+ class="restricted-subtitle text-center q-mt-sm"
35
+ data-testid="restricted-message"
36
+ >
37
+ {{ restrictedMessageParts.before
38
+ }}<strong>{{ restrictedMessageParts.bold }}</strong
39
+ >{{ restrictedMessageParts.after }}
40
+ </div>
41
+ </div>
42
+
43
+ <div v-if="USE_SSO" class="restricted-sso-section">
44
+ <div class="restricted-sso-label">{{ $t("auth.continueWith") }}</div>
45
+ <q-btn
46
+ data-testid="restricted-sso-btn"
47
+ data-cy="submit-sso-login"
48
+ unelevated
49
+ no-caps
50
+ dense
51
+ :loading="msalLoading"
52
+ :disable="msalLoading"
53
+ class="restricted-sso-button"
54
+ @click="emit('msalAuth')"
55
+ >
56
+ <img src="../../assets/logo-microsoft.svg" class="btn-icon" />
57
+ {{ $t("auth.microsoft") }}
58
+ </q-btn>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <div
64
+ v-else
3
65
  class="row justify-center full-height q-pl-sm q-pr-sm"
4
66
  style="background: #f4f5f6"
5
67
  data-testid="ask-password-change-form"
@@ -67,10 +129,10 @@
67
129
  data-cy="sendEmails"
68
130
  no-caps
69
131
  unelevated
132
+ :loading="loading"
70
133
  :label="$t('common.reset')"
71
134
  class="btn-min full-width"
72
135
  @click="sendEmail"
73
- v-close-popup
74
136
  />
75
137
  </q-card-actions>
76
138
  </div>
@@ -80,24 +142,68 @@
80
142
  <script lang="ts">
81
143
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
82
144
  // @ts-ignore
83
- import { defineComponent, ref } from "vue"
145
+ import { computed, defineComponent, ref } from "vue"
146
+ import { useI18n } from "vue-i18n"
147
+ import prompts from "app/quasar.extensions.json"
84
148
  import {
85
149
  ionMailOutline,
86
150
  ionPaperPlaneOutline,
87
151
  } from "@quasar/extras/ionicons-v5"
88
152
 
153
+ // Le message d'interdiction nomme "Microsoft" : il est mis en gras comme sur
154
+ // l'ecran restricted du magic link (FirstLogin.vue).
155
+ const RESTRICTED_MESSAGE_BOLD_KEYWORD = "Microsoft"
156
+
89
157
  export default defineComponent({
90
- emit: ["resetPassword", "close"],
158
+ name: "SendEmail",
159
+ props: {
160
+ // Compte SSO uniquement (403) : l'ecran "Reinitialisation impossible" est
161
+ // rendu dans cette meme page, a la place du formulaire.
162
+ restricted: {
163
+ type: Boolean,
164
+ default: false
165
+ },
166
+ loading: {
167
+ type: Boolean,
168
+ default: false
169
+ },
170
+ msalLoading: {
171
+ type: Boolean,
172
+ default: false
173
+ }
174
+ },
175
+ emit: ["resetPassword", "close", "msalAuth"],
91
176
  setup(props: any, { emit }: any) {
177
+ const { t } = useI18n()
178
+ const {
179
+ "@sowell/auth": { USE_SSO },
180
+ } = prompts
92
181
  const email = ref("")
93
182
  const sendEmail = () => {
94
183
  emit("resetPassword", email.value)
95
184
  }
96
185
 
186
+ const restrictedMessageParts = computed(() => {
187
+ const message = t("auth.restrictedDescription")
188
+ const keywordIndex = message.indexOf(RESTRICTED_MESSAGE_BOLD_KEYWORD)
189
+ if (keywordIndex === -1) {
190
+ return { before: message, bold: "", after: "" }
191
+ }
192
+ return {
193
+ before: message.slice(0, keywordIndex),
194
+ bold: RESTRICTED_MESSAGE_BOLD_KEYWORD,
195
+ after: message.slice(
196
+ keywordIndex + RESTRICTED_MESSAGE_BOLD_KEYWORD.length
197
+ )
198
+ }
199
+ })
200
+
97
201
  return {
98
202
  email,
99
203
  sendEmail,
100
204
  emit,
205
+ USE_SSO,
206
+ restrictedMessageParts,
101
207
  icons: {
102
208
  ionMailOutline,
103
209
  ionPaperPlaneOutline,
@@ -112,4 +218,97 @@ export default defineComponent({
112
218
  display: none;
113
219
  }
114
220
  }
221
+
222
+ .restricted-page {
223
+ min-height: 100vh;
224
+ background: #fff;
225
+ max-width: 100vw;
226
+ overflow-x: hidden;
227
+ }
228
+
229
+ .background-logo-container {
230
+ position: fixed;
231
+ top: -53px;
232
+ left: 0;
233
+ width: 100%;
234
+ height: 100vh;
235
+ display: flex;
236
+ justify-content: center;
237
+ align-items: center;
238
+ opacity: 0.25;
239
+ overflow: hidden;
240
+ }
241
+
242
+ .background-logo {
243
+ width: 100%;
244
+ height: 100%;
245
+ object-fit: cover;
246
+ max-width: 100%;
247
+ max-height: 100vh;
248
+ }
249
+
250
+ .top-bar {
251
+ z-index: 1;
252
+ max-width: 430px;
253
+ }
254
+
255
+ .back-link {
256
+ cursor: pointer;
257
+ color: #00668a;
258
+ font-size: 14px;
259
+ font-weight: 600;
260
+ }
261
+
262
+ .restricted-card {
263
+ z-index: 1;
264
+ max-width: 398px;
265
+ padding: 32px 24px;
266
+ margin-top: 24px;
267
+ }
268
+
269
+ .restricted-badge {
270
+ width: 82px;
271
+ height: 82px;
272
+ }
273
+
274
+ .restricted-title {
275
+ font-size: 24px;
276
+ font-weight: 600;
277
+ color: #20627f;
278
+ line-height: 1.4;
279
+ }
280
+
281
+ .restricted-subtitle {
282
+ font-size: 14px;
283
+ font-weight: 400;
284
+ color: #3e484f;
285
+ }
286
+
287
+ .restricted-sso-section {
288
+ width: 100%;
289
+ margin-top: 40px;
290
+ }
291
+
292
+ .restricted-sso-label {
293
+ font-size: 16px;
294
+ font-weight: 600;
295
+ color: #112240;
296
+ margin-bottom: 12px;
297
+ }
298
+
299
+ .restricted-sso-button {
300
+ width: 180px;
301
+ height: 36px;
302
+ border-radius: 8px;
303
+ background: #ebeeef;
304
+ font-size: 12px;
305
+ font-weight: 600;
306
+ color: #112240;
307
+ }
308
+
309
+ .btn-icon {
310
+ width: 16px;
311
+ height: 16px;
312
+ margin-right: 8px;
313
+ }
115
314
  </style>
@@ -85,7 +85,7 @@
85
85
  :disable="isSubmitBtnDisabled() || loading || msalLoading" />
86
86
  <div
87
87
  class="q-mb-lg q-mt-xl text-primary"
88
- @click="forgottenPassword = true"
88
+ @click="openForgottenPassword"
89
89
  data-cy="forgot-password"
90
90
  data-testid="ask-password-change-btn"
91
91
  style="font-size: 16px; font-weight: 500; cursor: pointer">
@@ -143,7 +143,13 @@
143
143
  transition-hide="fade"
144
144
  persistent
145
145
  data-testid="ask-password-change-modal">
146
- <send-email @resetPassword="resetPassword" @close="() => forgottenPassword = false" />
146
+ <send-email
147
+ :restricted="isResetRestricted"
148
+ :loading="loading"
149
+ :msal-loading="msalLoading"
150
+ @resetPassword="resetPassword"
151
+ @msalAuth="msalAuth"
152
+ @close="closeForgottenPassword" />
147
153
  </q-dialog>
148
154
  <q-dialog v-model="isReset" persistent>
149
155
  <sowell-message
@@ -170,7 +176,7 @@
170
176
  </template>
171
177
 
172
178
  <script lang="ts">
173
- import { defineComponent, reactive, onMounted } from "vue"
179
+ import { computed, defineComponent, reactive, onMounted } from "vue"
174
180
  import { AxiosError } from "axios"
175
181
  import * as packageInfo from "app/package.json"
176
182
 
@@ -210,6 +216,21 @@ export default defineComponent({
210
216
  const appVersion = reactive({
211
217
  versionNumber: ""
212
218
  })
219
+ // NOTE: 403 is returned when the company does not allow password reset
220
+ // (SSO-only account) - the "Reinitialisation impossible" screen is then
221
+ // rendered inside the email page itself, no extra dialog.
222
+ const isResetRestricted = computed(() => sendEmailCode.value === 403)
223
+
224
+ const openForgottenPassword = () => {
225
+ sendEmailCode.value = 0
226
+ forgottenPassword.value = true
227
+ }
228
+
229
+ const closeForgottenPassword = () => {
230
+ sendEmailCode.value = 0
231
+ forgottenPassword.value = false
232
+ }
233
+
213
234
  const resetPassword = async (email: string) => {
214
235
  const data = {
215
236
  auth: {
@@ -218,17 +239,19 @@ export default defineComponent({
218
239
  }
219
240
  }
220
241
  try {
221
- forgottenPassword.value = false
222
242
  await authState.resetPassword(data).then(() => {
223
243
  sendEmailCode.value = 200
244
+ forgottenPassword.value = false
224
245
  isReset.value = true
225
246
  })
226
247
  } catch (error) {
227
- // NOTE: 403 is returned when the company does not allow password reset
228
- // (SSO-only account) - the dialog then offers the Microsoft login instead.
229
- sendEmailCode.value =
230
- (error as AxiosError).response?.status === 403 ? 403 : 404
231
- isReset.value = true
248
+ if ((error as AxiosError).response?.status === 403) {
249
+ sendEmailCode.value = 403
250
+ } else {
251
+ sendEmailCode.value = 404
252
+ forgottenPassword.value = false
253
+ isReset.value = true
254
+ }
232
255
  }
233
256
  }
234
257
 
@@ -260,6 +283,9 @@ export default defineComponent({
260
283
  retypeEmail,
261
284
  msalAuth,
262
285
  handleSubmit,
286
+ isResetRestricted,
287
+ openForgottenPassword,
288
+ closeForgottenPassword,
263
289
  resetPassword,
264
290
  isMagicLinkEnabled,
265
291
  goToFirstLogin
@@ -39,8 +39,17 @@
39
39
  </q-input>
40
40
  </div>
41
41
  <div class="q-mx-lg q-mt-md input-container">
42
- <div class="input-label">
43
- {{ $t("auth.password") }}
42
+ <div class="row justify-between items-center no-wrap">
43
+ <div class="input-label" style="margin-bottom: 0">
44
+ {{ $t("auth.password") }}
45
+ </div>
46
+ <div
47
+ class="forgot-password-link"
48
+ @click="openForgottenPassword"
49
+ data-testid="ask-password-change-btn"
50
+ data-cy="forgot-password">
51
+ {{ $t("auth.forgottenPassword") }}
52
+ </div>
44
53
  </div>
45
54
  <q-input
46
55
  data-cy="password"
@@ -86,19 +95,12 @@
86
95
  </div>
87
96
  </q-btn>
88
97
  </div>
89
- <div
90
- class="forgot-password-link self-start"
91
- style="
92
- font-weight: 500;
93
- margin: 12px 0;
94
- text-decoration: underline;
95
- "
96
- @click="forgottenPassword = true"
97
- data-testid="ask-password-change-btn"
98
- data-cy="forgot-password">
99
- {{ $t("auth.forgottenPassword") }}
100
- </div>
101
98
  <template v-if="USE_SSO">
99
+ <div class="row items-center full-width q-mt-lg divider-row">
100
+ <div class="divider-line"></div>
101
+ <div class="divider-text">{{ $t("common.or") }}</div>
102
+ <div class="divider-line"></div>
103
+ </div>
102
104
  <div class="row justify-between full-width items-center q-mt-md">
103
105
  <!-- <q-btn
104
106
  data-testid="google-btn"
@@ -120,7 +122,7 @@
120
122
  <img
121
123
  src="../../assets/logo-microsoft.svg"
122
124
  class="btn-icon" />
123
- {{ $t("auth.continueWithMicrosoft") }}
125
+ {{ $t("auth.msalLogin") }}
124
126
  </q-btn>
125
127
  </div>
126
128
  </template>
@@ -161,8 +163,12 @@
161
163
  persistent
162
164
  data-testid="ask-password-change-modal">
163
165
  <send-email
166
+ :restricted="isResetRestricted"
167
+ :loading="loading"
168
+ :msal-loading="msalLoading"
164
169
  @resetPassword="resetPassword"
165
- @close="() => (forgottenPassword = false)" />
170
+ @msalAuth="msalAuth"
171
+ @close="closeForgottenPassword" />
166
172
  </q-dialog>
167
173
  <q-dialog v-model="isReset" persistent>
168
174
  <sowell-message
@@ -264,6 +270,20 @@ export default defineComponent({
264
270
 
265
271
  const isOtp = computed(() => IS_OTP.toLowerCase() === "y")
266
272
 
273
+ // NOTE: 403 = compte SSO uniquement. L'ecran "Reinitialisation impossible"
274
+ // est alors rendu dans la page de saisie de l'email, sans modale.
275
+ const isResetRestricted = computed(() => sendEmailCode.value === 403)
276
+
277
+ const openForgottenPassword = () => {
278
+ sendEmailCode.value = 0
279
+ forgottenPassword.value = true
280
+ }
281
+
282
+ const closeForgottenPassword = () => {
283
+ sendEmailCode.value = 0
284
+ forgottenPassword.value = false
285
+ }
286
+
267
287
  const resetPassword = async (email: string) => {
268
288
  const data = {
269
289
  auth: {
@@ -272,20 +292,20 @@ export default defineComponent({
272
292
  }
273
293
  }
274
294
  try {
275
- forgottenPassword.value = false
276
295
  await authState.resetPassword(data).then(() => {
277
296
  localStorage.set("emailResetPassword", email)
278
297
  sendEmailCode.value = 200
298
+ forgottenPassword.value = false
279
299
  isReset.value = true
280
300
  })
281
301
  } catch (error) {
282
302
  if ((error as AxiosError).response?.status === 403) {
283
- // NOTE: 403 is returned when the user is not allowed to reset the password
284
303
  sendEmailCode.value = 403
285
304
  } else {
286
305
  sendEmailCode.value = 404
306
+ forgottenPassword.value = false
307
+ isReset.value = true
287
308
  }
288
- isReset.value = true
289
309
  }
290
310
  }
291
311
 
@@ -335,6 +355,9 @@ export default defineComponent({
335
355
  isMagicLinkEnabled,
336
356
  msalAuth,
337
357
  handleSubmit,
358
+ isResetRestricted,
359
+ openForgottenPassword,
360
+ closeForgottenPassword,
338
361
  resetPassword,
339
362
  goToPrivacyPolicy,
340
363
  goToFirstLogin
@@ -398,10 +421,29 @@ export default defineComponent({
398
421
  }
399
422
 
400
423
  .forgot-password-link {
401
- font-size: 15px;
424
+ font-size: 14px;
402
425
  font-weight: 500;
403
426
  cursor: pointer;
404
427
  color: #001d3d;
428
+ text-decoration: underline;
429
+ }
430
+
431
+ .divider-row {
432
+ gap: 10px;
433
+ }
434
+
435
+ .divider-line {
436
+ flex: 1;
437
+ height: 1px;
438
+ background-color: #e5e7eb;
439
+ }
440
+
441
+ .divider-text {
442
+ font-size: 14px;
443
+ font-weight: 400;
444
+ color: #607680;
445
+ text-transform: uppercase;
446
+ white-space: nowrap;
405
447
  }
406
448
 
407
449
  .q-separator {
@@ -44,7 +44,7 @@
44
44
  >{{ restrictedMessageParts.after }}
45
45
  </div>
46
46
  </div>
47
- <div class="restricted-sso-section">
47
+ <div v-if="USE_SSO" class="restricted-sso-section">
48
48
  <div class="restricted-sso-label">{{ $t("auth.continueWith") }}</div>
49
49
  <q-btn
50
50
  data-testid="restricted-sso-btn"
@@ -311,7 +311,7 @@ export default defineComponent({
311
311
  openMailbox
312
312
  } = useMagicLink()
313
313
 
314
- const { msalLoading, msalAuth } = useAuth()
314
+ const { msalLoading, msalAuth, USE_SSO } = useAuth()
315
315
 
316
316
  const icons = {
317
317
  ionWarningOutline,
@@ -376,6 +376,7 @@ export default defineComponent({
376
376
  restrictedMessageParts,
377
377
  msalLoading,
378
378
  msalAuth,
379
+ USE_SSO,
379
380
  isActivateContext,
380
381
  maskedEmail,
381
382
  icons,