@sowell/quasar-app-extension-auth 1.8.5 → 1.8.7

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.5",
3
+ "version": "1.8.7",
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>
@@ -2,10 +2,12 @@
2
2
  <q-layout>
3
3
  <div class="row window-height">
4
4
  <div class="col column content-center bgWhite">
5
- <div class="q-mt-xl">
5
+ <div class="q-mt-xl logo-block">
6
6
  <img src="../../assets/Sowell-logo.svg" style="width: 210px" />
7
7
  </div>
8
- <div class="column col content-center justify-center center">
8
+ <div
9
+ class="column col content-center justify-center center"
10
+ :class="{ 'center-no-offset': isMagicLinkEnabled }">
9
11
  <div class="q-pl-md q-pr-md" style="max-width: 500px">
10
12
  <h4 class="title">{{ WELCOME_TITLE }}</h4>
11
13
  <div class="subtitle">{{ WELCOME_SUBTITLE }}</div>
@@ -276,8 +278,20 @@ export default defineComponent({
276
278
  margin-top: -115px;
277
279
  }
278
280
 
281
+ /* Le bloc "Activer mon compte" (magiclink) rallonge la colonne : le decalage
282
+ negatif de recentrage faisait alors chevaucher le titre avec le logo. */
283
+ .center-no-offset {
284
+ margin-top: 0;
285
+ }
286
+
287
+ /* Le logo ne doit jamais etre compresse ni recouvert quand la colonne deborde. */
288
+ .logo-block {
289
+ flex: none;
290
+ }
291
+
279
292
  .bgWhite {
280
293
  background: #ffffff;
294
+ overflow-y: auto;
281
295
  }
282
296
  .bgBlue {
283
297
  background: var(--q-primary);
@@ -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
@@ -93,7 +93,7 @@
93
93
  margin: 12px 0;
94
94
  text-decoration: underline;
95
95
  "
96
- @click="forgottenPassword = true"
96
+ @click="openForgottenPassword"
97
97
  data-testid="ask-password-change-btn"
98
98
  data-cy="forgot-password">
99
99
  {{ $t("auth.forgottenPassword") }}
@@ -161,8 +161,12 @@
161
161
  persistent
162
162
  data-testid="ask-password-change-modal">
163
163
  <send-email
164
+ :restricted="isResetRestricted"
165
+ :loading="loading"
166
+ :msal-loading="msalLoading"
164
167
  @resetPassword="resetPassword"
165
- @close="() => (forgottenPassword = false)" />
168
+ @msalAuth="msalAuth"
169
+ @close="closeForgottenPassword" />
166
170
  </q-dialog>
167
171
  <q-dialog v-model="isReset" persistent>
168
172
  <sowell-message
@@ -264,6 +268,20 @@ export default defineComponent({
264
268
 
265
269
  const isOtp = computed(() => IS_OTP.toLowerCase() === "y")
266
270
 
271
+ // NOTE: 403 = compte SSO uniquement. L'ecran "Reinitialisation impossible"
272
+ // est alors rendu dans la page de saisie de l'email, sans modale.
273
+ const isResetRestricted = computed(() => sendEmailCode.value === 403)
274
+
275
+ const openForgottenPassword = () => {
276
+ sendEmailCode.value = 0
277
+ forgottenPassword.value = true
278
+ }
279
+
280
+ const closeForgottenPassword = () => {
281
+ sendEmailCode.value = 0
282
+ forgottenPassword.value = false
283
+ }
284
+
267
285
  const resetPassword = async (email: string) => {
268
286
  const data = {
269
287
  auth: {
@@ -272,20 +290,20 @@ export default defineComponent({
272
290
  }
273
291
  }
274
292
  try {
275
- forgottenPassword.value = false
276
293
  await authState.resetPassword(data).then(() => {
277
294
  localStorage.set("emailResetPassword", email)
278
295
  sendEmailCode.value = 200
296
+ forgottenPassword.value = false
279
297
  isReset.value = true
280
298
  })
281
299
  } catch (error) {
282
300
  if ((error as AxiosError).response?.status === 403) {
283
- // NOTE: 403 is returned when the user is not allowed to reset the password
284
301
  sendEmailCode.value = 403
285
302
  } else {
286
303
  sendEmailCode.value = 404
304
+ forgottenPassword.value = false
305
+ isReset.value = true
287
306
  }
288
- isReset.value = true
289
307
  }
290
308
  }
291
309
 
@@ -335,6 +353,9 @@ export default defineComponent({
335
353
  isMagicLinkEnabled,
336
354
  msalAuth,
337
355
  handleSubmit,
356
+ isResetRestricted,
357
+ openForgottenPassword,
358
+ closeForgottenPassword,
338
359
  resetPassword,
339
360
  goToPrivacyPolicy,
340
361
  goToFirstLogin
@@ -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,
@@ -2,10 +2,14 @@
2
2
  <q-layout>
3
3
  <div class="row window-height">
4
4
  <div class="col column content-center bgWhite">
5
- <div class="q-mt-xl custom-screen">
5
+ <div
6
+ class="q-mt-xl custom-screen"
7
+ :class="{ 'custom-screen-compact': isMagicLinkEnabled }">
6
8
  <img src="../../assets/Sowell-logo.svg" style="width: 210px" />
7
9
  </div>
8
- <div class="column col content-center justify-center center">
10
+ <div
11
+ class="column col content-center justify-center center"
12
+ :class="{ 'center-no-offset': isMagicLinkEnabled }">
9
13
  <div class="q-pl-md q-pr-md" style="max-width: 500px">
10
14
  <h4 class="title">{{ WELCOME_TITLE }}</h4>
11
15
  <div class="subtitle">{{ WELCOME_SUBTITLE }}</div>
@@ -396,8 +400,20 @@ export default defineComponent({
396
400
  margin-top: -115px;
397
401
  }
398
402
 
403
+ /* Le bloc "Activer mon compte" (magiclink) rallonge la colonne : le decalage
404
+ negatif de recentrage faisait alors chevaucher le titre avec le logo. */
405
+ .center-no-offset {
406
+ margin-top: 0;
407
+ }
408
+
409
+ /* Le logo ne doit jamais etre compresse ni recouvert quand la colonne deborde. */
410
+ .custom-screen {
411
+ flex: none;
412
+ }
413
+
399
414
  .bgWhite {
400
415
  background: #ffffff;
416
+ overflow-y: auto;
401
417
  }
402
418
  .bgBlue {
403
419
  background: var(--q-primary);
@@ -429,6 +445,11 @@ export default defineComponent({
429
445
  margin-top: 15px;
430
446
  margin-bottom: 125px;
431
447
  }
448
+ /* Avec le bloc magiclink la colonne est bien plus haute : la grande marge
449
+ sous le logo ferait deborder le formulaire hors de l'ecran. */
450
+ .custom-screen-compact {
451
+ margin-bottom: 24px;
452
+ }
432
453
  .subtitle {
433
454
  margin-bottom: 20px !important;
434
455
  }