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

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.3",
3
+ "version": "1.8.5",
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",
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="82" height="82" viewBox="0 0 82 82" fill="none">
2
+ <path d="M20.467 13.2638C20.467 13.2638 32.783 22.7041 51.5063 5.05236C68.1346 -10.6241 81.7117 14.1097 81.8067 25.9544C81.9298 41.298 65.1784 53.5748 73.3078 63.6526C81.4372 73.7304 57.1861 90.3722 44.116 75.9698C27.8572 58.0538 23.4525 72.6106 14.185 72.6106C7.53374 72.6106 -6.12216 55.9168 3.09953 43.4971C10.8594 33.0461 6.6273 29.5776 4.57785 25.9544C1.62159 20.7288 8.64242 6.54534 20.467 13.2638Z" fill="#FFEFCF"/>
3
+ <path d="M63.1242 56.9728L44.1383 21.3586C42.7926 18.8336 39.2078 18.8336 37.861 21.3586L18.8762 56.9728C18.584 57.5209 18.4375 58.1363 18.4511 58.7587C18.4646 59.3811 18.6376 59.9894 18.9533 60.5241C19.269 61.0588 19.7165 61.5017 20.2522 61.8095C20.788 62.1174 21.3935 62.2797 22.0099 62.2806H59.9849C60.6017 62.2807 61.208 62.1191 61.7445 61.8116C62.281 61.5042 62.7293 61.0614 63.0457 60.5265C63.362 59.9916 63.5355 59.383 63.5493 58.7601C63.5631 58.1372 63.4166 57.5214 63.1242 56.9728V56.9728ZM41.0002 56.7669C40.5595 56.7669 40.1288 56.6349 39.7624 56.3876C39.396 56.1403 39.1104 55.7888 38.9418 55.3776C38.7732 54.9664 38.729 54.5139 38.815 54.0773C38.901 53.6408 39.1132 53.2397 39.4248 52.925C39.7364 52.6103 40.1333 52.3959 40.5655 52.3091C40.9977 52.2223 41.4457 52.2668 41.8528 52.4372C42.2599 52.6075 42.6079 52.896 42.8527 53.266C43.0975 53.6361 43.2282 54.0712 43.2282 54.5164C43.2282 54.8119 43.1706 55.1045 43.0586 55.3776C42.9466 55.6506 42.7825 55.8987 42.5756 56.1077C42.3687 56.3167 42.1231 56.4824 41.8528 56.5955C41.5825 56.7086 41.2928 56.7669 41.0002 56.7669ZM43.4198 34.1325L42.7804 47.8605C42.7804 48.338 42.5926 48.7959 42.2583 49.1336C41.924 49.4712 41.4707 49.6609 40.998 49.6609C40.5252 49.6609 40.0719 49.4712 39.7376 49.1336C39.4034 48.7959 39.2156 48.338 39.2156 47.8605L38.5761 34.1381C38.5618 33.8102 38.6129 33.4827 38.7265 33.1752C38.8401 32.8677 39.0138 32.5864 39.2373 32.3481C39.4608 32.1099 39.7295 31.9195 40.0274 31.7884C40.3253 31.6573 40.6463 31.5881 40.9712 31.5849H40.9946C41.3218 31.5847 41.6456 31.6515 41.9465 31.7812C42.2474 31.9109 42.5191 32.1009 42.7453 32.3396C42.9716 32.5783 43.1476 32.8608 43.2627 33.1701C43.3779 33.4794 43.4298 33.8091 43.4153 34.1392L43.4198 34.1325Z" fill="#FFBD3D"/>
4
+ </svg>
@@ -12,9 +12,13 @@
12
12
  src="../../assets/success.svg"
13
13
  style="width: 100px; height: 80px" />
14
14
  <img
15
- v-if="emailNotSend"
15
+ v-if="emailNotSend && !isForbidden"
16
16
  src="../../assets/error.svg"
17
17
  style="width: 100px; height: 80px" />
18
+ <img
19
+ v-if="isForbidden"
20
+ src="../../assets/restricted.svg"
21
+ style="width: 82px; height: 82px" />
18
22
  </q-card-section>
19
23
 
20
24
  <q-card-section class="q-pt-none q-pl-lg q-pr-lg">
@@ -30,7 +34,27 @@
30
34
  data-testid="message-dialog"
31
35
  class="text-subtitle1 q-mt-sm text-center"
32
36
  style="font-size: 16px; font-weight: 400">
33
- {{ messageSubtitle }}
37
+ {{ messageSubtitleParts.before
38
+ }}<strong>{{ messageSubtitleParts.bold }}</strong
39
+ >{{ messageSubtitleParts.after }}
40
+ </div>
41
+ <div v-if="isForbidden" class="forbidden-sso-section">
42
+ <div class="forbidden-sso-label">{{ $t("auth.continueWith") }}</div>
43
+ <q-btn
44
+ data-testid="forbidden-sso-btn"
45
+ data-cy="submit-sso-login"
46
+ unelevated
47
+ no-caps
48
+ dense
49
+ :loading="msalLoading"
50
+ :disable="msalLoading"
51
+ class="forbidden-sso-button"
52
+ @click="onMsalAuth">
53
+ <img
54
+ src="../../assets/logo-microsoft.svg"
55
+ class="forbidden-sso-icon" />
56
+ {{ $t("auth.microsoft") }}
57
+ </q-btn>
34
58
  </div>
35
59
  <div
36
60
  class="q-mt-sm text-center q-mb-xl"
@@ -73,6 +97,8 @@
73
97
  import { defineComponent, computed } from "vue"
74
98
  import { useI18n } from "vue-i18n"
75
99
 
100
+ const FORBIDDEN_MESSAGE_BOLD_KEYWORD = "Microsoft"
101
+
76
102
  export default defineComponent({
77
103
  props: {
78
104
  emailResetPassword: {
@@ -84,17 +110,22 @@ export default defineComponent({
84
110
  loading: {
85
111
  type: Boolean
86
112
  },
113
+ msalLoading: {
114
+ type: Boolean
115
+ },
87
116
  passwordUpdated: {
88
117
  type: Boolean
89
118
  }
90
119
  },
91
- emit: ["retypeEmail", "goToLoginPage", "close"],
120
+ emit: ["retypeEmail", "goToLoginPage", "close", "msalAuth"],
92
121
  setup(props: any, { emit }: any) {
93
122
  const { t } = useI18n()
94
123
 
124
+ const isForbidden = computed(() => props.sendEmailCode === 403)
125
+
95
126
  const messageSubtitle = computed(() => {
96
127
  if (props.sendEmailCode === 403) {
97
- return t("auth.forbidden.subtitle")
128
+ return t("auth.restrictedDescription")
98
129
  }
99
130
  return props.sendEmailCode === 200
100
131
  ? t("auth.emailSent")
@@ -104,7 +135,7 @@ export default defineComponent({
104
135
  })
105
136
  const messageTitle = computed(() => {
106
137
  if (props.sendEmailCode === 403) {
107
- return t("auth.forbidden.title")
138
+ return t("auth.restrictedTitle")
108
139
  }
109
140
  return props.sendEmailCode === 200
110
141
  ? t("auth.emailVerification")
@@ -113,6 +144,23 @@ export default defineComponent({
113
144
  : t("auth.passwordChanged")
114
145
  })
115
146
 
147
+ // The forbidden message names "Microsoft" - it is emphasised the same way
148
+ // as on the magic link restricted screen.
149
+ const messageSubtitleParts = computed(() => {
150
+ const message = messageSubtitle.value
151
+ const keywordIndex = message.indexOf(FORBIDDEN_MESSAGE_BOLD_KEYWORD)
152
+ if (!isForbidden.value || keywordIndex === -1) {
153
+ return { before: message, bold: "", after: "" }
154
+ }
155
+ return {
156
+ before: message.slice(0, keywordIndex),
157
+ bold: FORBIDDEN_MESSAGE_BOLD_KEYWORD,
158
+ after: message.slice(
159
+ keywordIndex + FORBIDDEN_MESSAGE_BOLD_KEYWORD.length
160
+ )
161
+ }
162
+ })
163
+
116
164
  const emailNotSend = computed(
117
165
  () => props.sendEmailCode !== 200 && !props.passwordUpdated
118
166
  )
@@ -124,6 +172,10 @@ export default defineComponent({
124
172
  emit("retypeEmail")
125
173
  }
126
174
 
175
+ const onMsalAuth = () => {
176
+ emit("msalAuth")
177
+ }
178
+
127
179
  const handlePasswordUpdate = () => {
128
180
  if (props.passwordUpdated) {
129
181
  emit("goToLoginPage")
@@ -134,12 +186,49 @@ export default defineComponent({
134
186
 
135
187
  return {
136
188
  messageSubtitle,
189
+ messageSubtitleParts,
137
190
  messageTitle,
138
191
  emailNotSend,
139
192
  emailSended,
193
+ isForbidden,
140
194
  handlePasswordUpdate,
141
- retypeEmail
195
+ retypeEmail,
196
+ onMsalAuth
142
197
  }
143
198
  }
144
199
  })
145
200
  </script>
201
+
202
+ <style scoped lang="scss">
203
+ .forbidden-sso-section {
204
+ margin-top: 24px;
205
+ text-align: center;
206
+ }
207
+
208
+ .forbidden-sso-label {
209
+ font-size: 15px;
210
+ font-weight: 600;
211
+ color: #112240;
212
+ margin-bottom: 12px;
213
+ }
214
+
215
+ .forbidden-sso-button {
216
+ width: 180px;
217
+ height: 36px;
218
+ border-radius: 8px;
219
+ background: #ebeeef;
220
+ font-size: 12px;
221
+ font-weight: 600;
222
+ color: #112240;
223
+ }
224
+
225
+ .forbidden-sso-button:hover {
226
+ background: #e0e5e7;
227
+ }
228
+
229
+ .forbidden-sso-icon {
230
+ width: 16px;
231
+ height: 16px;
232
+ margin-right: 8px;
233
+ }
234
+ </style>
@@ -29,7 +29,8 @@
29
29
  class="text-subtitle1 q-mt-sm text-center text-dark"
30
30
  data-testid="message-dialog"
31
31
  >
32
- {{ content }}
32
+ {{ contentParts.before
33
+ }}<strong>{{ contentParts.bold }}</strong>{{ contentParts.after }}
33
34
  </div>
34
35
  </q-card-section>
35
36
  <q-card-actions class="row full-width justify-center q-mb-lg">
@@ -56,6 +57,7 @@ import { defineComponent, computed } from "vue";
56
57
  import userNotFound from "../../assets/User-not-found.svg";
57
58
  import passwordUpdated from "../../assets/Ok-illustration.svg";
58
59
  import emailVerification from "../../assets/Email-sent.svg";
60
+ import restricted from "../../assets/restricted.svg";
59
61
  import { ionCloseOutline } from "@quasar/extras/ionicons-v5";
60
62
 
61
63
  export default defineComponent({
@@ -66,6 +68,9 @@ export default defineComponent({
66
68
  image: { type: String, required: true },
67
69
  passwordUpdated: { type: Boolean },
68
70
  isOtp: { type: Boolean, default: false, required: false },
71
+ // Emphasises one word of `content` - used to bold "Microsoft" on the
72
+ // restricted / SSO-only message, like the magic link screens do.
73
+ boldKeyword: { type: String, default: "", required: false },
69
74
  },
70
75
  emit: ["goToLoginPage", "close"],
71
76
  setup(props: any) {
@@ -79,12 +84,29 @@ export default defineComponent({
79
84
 
80
85
  case "emailVerification":
81
86
  return emailVerification;
87
+
88
+ case "restricted":
89
+ return restricted;
82
90
  default:
83
91
  return null
84
92
  }
85
93
  })
86
94
 
87
- return { imageValue, icons: { ionCloseOutline } };
95
+ const contentParts = computed(() => {
96
+ const content = props.content as string;
97
+ const keyword = props.boldKeyword as string;
98
+ const keywordIndex = keyword ? content.indexOf(keyword) : -1;
99
+ if (keywordIndex === -1) {
100
+ return { before: content, bold: "", after: "" };
101
+ }
102
+ return {
103
+ before: content.slice(0, keywordIndex),
104
+ bold: keyword,
105
+ after: content.slice(keywordIndex + keyword.length),
106
+ };
107
+ });
108
+
109
+ return { imageValue, contentParts, icons: { ionCloseOutline } };
88
110
  },
89
111
  });
90
112
  </script>
@@ -114,6 +114,7 @@
114
114
  :content="infoDialog.content"
115
115
  :image="infoDialog.image"
116
116
  :isOtp="true"
117
+ :boldKeyword="infoDialog.isForbidden ? 'Microsoft' : ''"
117
118
  >
118
119
  <q-btn
119
120
  v-if="requestedOtp"
@@ -134,6 +135,7 @@
134
135
 
135
136
  <script lang="ts">
136
137
  import { defineComponent, ref, computed } from "vue"
138
+ import { AxiosError } from "axios"
137
139
  import { useI18n } from "vue-i18n"
138
140
  import isEmail from "validator/es/lib/isEmail"
139
141
  import { useAuthState } from "../../injects"
@@ -180,7 +182,8 @@ export default defineComponent({
180
182
  const infoDialog = ref({
181
183
  title: "",
182
184
  content: "",
183
- image: ""
185
+ image: "",
186
+ isForbidden: false
184
187
  })
185
188
 
186
189
  const submitBtnDisabled = computed(
@@ -204,6 +207,7 @@ export default defineComponent({
204
207
  await authState.resetPassword(data).then(() => {
205
208
  localStorage.setItem("emailResetPassword", email)
206
209
  infoDialog.value = {
210
+ isForbidden: false,
207
211
  title: t("auth.mailVerificationTitle"),
208
212
  content:
209
213
  IS_OTP.toLowerCase() === "y"
@@ -215,13 +219,25 @@ export default defineComponent({
215
219
  isNotification.value = true
216
220
  requestedOtp.value = true
217
221
  })
218
- } catch {
222
+ } catch (error) {
219
223
  requestedOtp.value = false
220
- infoDialog.value = {
221
- title: t("auth.userNotFoundTitle"),
222
- content: t("auth.userNotFoundSubtitle"),
223
- image: "userNotFound"
224
- }
224
+ // NOTE: 403 is returned when the company does not allow password reset
225
+ // (SSO-only account). The admin interface has no SSO entry point, so we
226
+ // only tell the user to reach out to their organisation.
227
+ infoDialog.value =
228
+ (error as AxiosError).response?.status === 403
229
+ ? {
230
+ isForbidden: true,
231
+ title: t("auth.restrictedTitle"),
232
+ content: t("auth.restrictedDescription"),
233
+ image: "restricted"
234
+ }
235
+ : {
236
+ isForbidden: false,
237
+ title: t("auth.userNotFoundTitle"),
238
+ content: t("auth.userNotFoundSubtitle"),
239
+ image: "userNotFound"
240
+ }
225
241
  isNotification.value = true
226
242
  }
227
243
  }
@@ -150,7 +150,9 @@
150
150
  :emailResetPassword="emailResetPassword"
151
151
  :sendEmailCode="sendEmailCode"
152
152
  :loading="loading"
153
- @retypeEmail="retypeEmail"
153
+ :msalLoading="msalLoading"
154
+ @retypeEmail="retypeEmail"
155
+ @msalAuth="msalAuth"
154
156
  @close="() => isReset= false"/>
155
157
  </q-dialog>
156
158
  </div>
@@ -169,6 +171,7 @@
169
171
 
170
172
  <script lang="ts">
171
173
  import { defineComponent, reactive, onMounted } from "vue"
174
+ import { AxiosError } from "axios"
172
175
  import * as packageInfo from "app/package.json"
173
176
 
174
177
  import SendEmail from "../dialogs/SendEmail.vue"
@@ -220,8 +223,11 @@ export default defineComponent({
220
223
  sendEmailCode.value = 200
221
224
  isReset.value = true
222
225
  })
223
- } catch {
224
- sendEmailCode.value = 404
226
+ } 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
225
231
  isReset.value = true
226
232
  }
227
233
  }
@@ -169,7 +169,9 @@
169
169
  :emailResetPassword="emailResetPassword"
170
170
  :sendEmailCode="sendEmailCode"
171
171
  :loading="loading"
172
+ :msalLoading="msalLoading"
172
173
  @retypeEmail="retypeEmail"
174
+ @msalAuth="msalAuth"
173
175
  @close="() => (isReset = false)">
174
176
  <q-btn
175
177
  v-if="sendEmailCode === 200 && isOtp"
@@ -23,28 +23,43 @@
23
23
  <template v-if="restricted">
24
24
  <div class="column items-center header-section">
25
25
  <div class="restricted-badge">
26
- <svg xmlns="http://www.w3.org/2000/svg" width="82" height="82" viewBox="0 0 82 82" fill="none" class="restricted-blob">
27
- <path d="M20.467 13.2638C20.467 13.2638 32.783 22.7041 51.5063 5.05236C68.1346 -10.6241 81.7117 14.1097 81.8067 25.9544C81.9298 41.298 65.1784 53.5748 73.3078 63.6526C81.4372 73.7304 57.1861 90.3722 44.116 75.9698C27.8572 58.0538 23.4525 72.6106 14.185 72.6106C7.53374 72.6106 -6.12216 55.9168 3.09953 43.4971C10.8594 33.0461 6.6273 29.5776 4.57785 25.9544C1.62159 20.7288 8.64242 6.54534 20.467 13.2638Z" fill="#FFEFCF"/>
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="82"
29
+ height="82"
30
+ viewBox="0 0 82 82"
31
+ fill="none">
32
+ <path
33
+ d="M20.467 13.2638C20.467 13.2638 32.783 22.7041 51.5063 5.05236C68.1346 -10.6241 81.7117 14.1097 81.8067 25.9544C81.9298 41.298 65.1784 53.5748 73.3078 63.6526C81.4372 73.7304 57.1861 90.3722 44.116 75.9698C27.8572 58.0538 23.4525 72.6106 14.185 72.6106C7.53374 72.6106 -6.12216 55.9168 3.09953 43.4971C10.8594 33.0461 6.6273 29.5776 4.57785 25.9544C1.62159 20.7288 8.64242 6.54534 20.467 13.2638Z"
34
+ fill="#FFEFCF" />
35
+ <path
36
+ d="M63.1242 56.9728L44.1383 21.3586C42.7926 18.8336 39.2078 18.8336 37.861 21.3586L18.8762 56.9728C18.584 57.5209 18.4375 58.1363 18.4511 58.7587C18.4646 59.3811 18.6376 59.9894 18.9533 60.5241C19.269 61.0588 19.7165 61.5017 20.2522 61.8095C20.788 62.1174 21.3935 62.2797 22.0099 62.2806H59.9849C60.6017 62.2807 61.208 62.1191 61.7445 61.8116C62.281 61.5042 62.7293 61.0614 63.0457 60.5265C63.362 59.9916 63.5355 59.383 63.5493 58.7601C63.5631 58.1372 63.4166 57.5214 63.1242 56.9728V56.9728ZM41.0002 56.7669C40.5595 56.7669 40.1288 56.6349 39.7624 56.3876C39.396 56.1403 39.1104 55.7888 38.9418 55.3776C38.7732 54.9664 38.729 54.5139 38.815 54.0773C38.901 53.6408 39.1132 53.2397 39.4248 52.925C39.7364 52.6103 40.1333 52.3959 40.5655 52.3091C40.9977 52.2223 41.4457 52.2668 41.8528 52.4372C42.2599 52.6075 42.6079 52.896 42.8527 53.266C43.0975 53.6361 43.2282 54.0712 43.2282 54.5164C43.2282 54.8119 43.1706 55.1045 43.0586 55.3776C42.9466 55.6506 42.7825 55.8987 42.5756 56.1077C42.3687 56.3167 42.1231 56.4824 41.8528 56.5955C41.5825 56.7086 41.2928 56.7669 41.0002 56.7669ZM43.4198 34.1325L42.7804 47.8605C42.7804 48.338 42.5926 48.7959 42.2583 49.1336C41.924 49.4712 41.4707 49.6609 40.998 49.6609C40.5252 49.6609 40.0719 49.4712 39.7376 49.1336C39.4034 48.7959 39.2156 48.338 39.2156 47.8605L38.5761 34.1381C38.5618 33.8102 38.6129 33.4827 38.7265 33.1752C38.8401 32.8677 39.0138 32.5864 39.2373 32.3481C39.4608 32.1099 39.7295 31.9195 40.0274 31.7884C40.3253 31.6573 40.6463 31.5881 40.9712 31.5849H40.9946C41.3218 31.5847 41.6456 31.6515 41.9465 31.7812C42.2474 31.9109 42.5191 32.1009 42.7453 32.3396C42.9716 32.5783 43.1476 32.8608 43.2627 33.1701C43.3779 33.4794 43.4298 33.8091 43.4153 34.1392L43.4198 34.1325Z"
37
+ fill="#FFBD3D" />
28
38
  </svg>
29
- <div class="restricted-circle">
30
- <q-icon name="lock" size="24px" color="white" />
31
- </div>
32
39
  </div>
33
40
  <div class="title text-center q-mt-md">{{ $t("auth.restrictedTitle") }}</div>
34
- <div class="subtitle text-center q-mt-sm">
35
- {{ restrictedMessage || $t("auth.restrictedDescription") }}
41
+ <div class="subtitle text-center q-mt-sm" data-testid="restricted-message">
42
+ {{ restrictedMessageParts.before
43
+ }}<strong>{{ restrictedMessageParts.bold }}</strong
44
+ >{{ restrictedMessageParts.after }}
36
45
  </div>
37
46
  </div>
38
- <q-btn
39
- data-testid="restricted-back-btn"
40
- color="primary"
41
- unelevated
42
- no-caps
43
- text-color="white"
44
- class="full-width q-mt-lg submit-button"
45
- @click="goBackToLogin">
46
- {{ $t("auth.backToLoginFull") }}
47
- </q-btn>
47
+ <div class="restricted-sso-section">
48
+ <div class="restricted-sso-label">{{ $t("auth.continueWith") }}</div>
49
+ <q-btn
50
+ data-testid="restricted-sso-btn"
51
+ data-cy="submit-sso-login"
52
+ unelevated
53
+ no-caps
54
+ dense
55
+ :loading="msalLoading"
56
+ :disable="msalLoading"
57
+ class="sso-btn restricted-sso-button"
58
+ @click="msalAuth">
59
+ <img src="../../assets/logo-microsoft.svg" class="btn-icon" />
60
+ {{ $t("auth.microsoft") }}
61
+ </q-btn>
62
+ </div>
48
63
  </template>
49
64
 
50
65
  <template v-else-if="step === 'email'">
@@ -260,6 +275,15 @@ import {
260
275
  ionInformationCircleOutline
261
276
  } from "@quasar/extras/ionicons-v5"
262
277
  import { useMagicLink } from "composables/useMagicLink"
278
+ import { useAuth } from "composables/auth"
279
+
280
+ interface RestrictedMessageParts {
281
+ before: string
282
+ bold: string
283
+ after: string
284
+ }
285
+
286
+ const RESTRICTED_MESSAGE_BOLD_KEYWORD = "Microsoft"
263
287
 
264
288
  export default defineComponent({
265
289
  name: "FirstLogin",
@@ -277,7 +301,6 @@ export default defineComponent({
277
301
  isLoading,
278
302
  resendCountdown,
279
303
  restricted,
280
- restrictedMessage,
281
304
  isActivateContext,
282
305
  maskedEmail,
283
306
  requestChannels,
@@ -288,11 +311,28 @@ export default defineComponent({
288
311
  openMailbox
289
312
  } = useMagicLink()
290
313
 
314
+ const { msalLoading, msalAuth } = useAuth()
315
+
291
316
  const icons = {
292
317
  ionWarningOutline,
293
318
  ionInformationCircleOutline
294
319
  }
295
320
 
321
+ const restrictedMessageParts = computed<RestrictedMessageParts>(() => {
322
+ const message = t("auth.restrictedDescription")
323
+ const keywordIndex = message.indexOf(RESTRICTED_MESSAGE_BOLD_KEYWORD)
324
+ if (keywordIndex === -1) {
325
+ return { before: message, bold: "", after: "" }
326
+ }
327
+ return {
328
+ before: message.slice(0, keywordIndex),
329
+ bold: RESTRICTED_MESSAGE_BOLD_KEYWORD,
330
+ after: message.slice(
331
+ keywordIndex + RESTRICTED_MESSAGE_BOLD_KEYWORD.length
332
+ )
333
+ }
334
+ })
335
+
296
336
  const translateOrFallback = (key: string, fallbackKey: string): string =>
297
337
  te(key) ? t(key) : t(fallbackKey)
298
338
 
@@ -333,7 +373,9 @@ export default defineComponent({
333
373
  isLoading,
334
374
  resendCountdown,
335
375
  restricted,
336
- restrictedMessage,
376
+ restrictedMessageParts,
377
+ msalLoading,
378
+ msalAuth,
337
379
  isActivateContext,
338
380
  maskedEmail,
339
381
  icons,
@@ -448,26 +490,6 @@ export default defineComponent({
448
490
  height: 82px;
449
491
  }
450
492
 
451
- .restricted-blob {
452
- position: absolute;
453
- top: 0;
454
- left: 0;
455
- }
456
-
457
- .restricted-circle {
458
- position: absolute;
459
- top: 50%;
460
- left: 50%;
461
- transform: translate(-50%, -50%);
462
- width: 45px;
463
- height: 45px;
464
- border-radius: 9999px;
465
- background: #ffce6e;
466
- display: flex;
467
- align-items: center;
468
- justify-content: center;
469
- }
470
-
471
493
  .title {
472
494
  font-size: 24px;
473
495
  font-weight: 600;
@@ -529,6 +551,39 @@ export default defineComponent({
529
551
  font-weight: 500;
530
552
  }
531
553
 
554
+ .restricted-sso-section {
555
+ width: 100%;
556
+ margin-top: 40px;
557
+ }
558
+
559
+ .restricted-sso-label {
560
+ font-size: 16px;
561
+ font-weight: 600;
562
+ color: #112240;
563
+ margin-bottom: 12px;
564
+ }
565
+
566
+ .sso-btn {
567
+ font-size: 14px;
568
+ border-radius: 8px;
569
+ height: 50px;
570
+ font-weight: 600;
571
+ background: #ebeeef;
572
+ color: #112240;
573
+ }
574
+
575
+ .btn-icon {
576
+ width: 16px;
577
+ height: 16px;
578
+ margin-right: 8px;
579
+ }
580
+
581
+ .restricted-sso-button {
582
+ width: 180px;
583
+ height: 36px;
584
+ font-size: 12px;
585
+ }
586
+
532
587
  .resend-button {
533
588
  border-radius: 15px;
534
589
  height: 56px;
@@ -154,10 +154,39 @@
154
154
  :content="infoDialog.content"
155
155
  :image="infoDialog.image"
156
156
  :isOtp="isOtp"
157
+ :boldKeyword="infoDialog.isForbidden ? 'Microsoft' : ''"
157
158
  @close="() => (isNotification = false)">
159
+ <div v-if="infoDialog.isForbidden" class="column items-center">
160
+ <div class="forbidden-sso-label">{{ $t("auth.connectWith") }}</div>
161
+ <q-btn
162
+ data-testid="forbidden-sso-btn"
163
+ data-cy="submit-sso-login"
164
+ unelevated
165
+ no-caps
166
+ dense
167
+ :loading="msalLoading"
168
+ :disable="msalLoading"
169
+ class="forbidden-sso-button"
170
+ @click="msalAuth">
171
+ <img
172
+ src="../../assets/logo-microsoft.svg"
173
+ class="forbidden-sso-icon" />
174
+ {{ $t("auth.microsoft") }}
175
+ </q-btn>
176
+ <q-btn
177
+ data-testid="close-btn"
178
+ class="bg-white submitBtn q-mt-md"
179
+ unelevated
180
+ outline
181
+ no-caps
182
+ :label="$t('auth.close')"
183
+ color="primary"
184
+ @click="() => (isNotification = false)"
185
+ v-close-popup />
186
+ </div>
158
187
  <q-btn
159
188
  :data-testid="infoDialog.canGoNext ? 'next-btn' : 'close-btn'"
160
- v-if="isOtp"
189
+ v-else-if="isOtp"
161
190
  class="bg-white submitBtn"
162
191
  unelevated
163
192
  outline
@@ -226,7 +255,8 @@ export default defineComponent({
226
255
  const infoDialog = ref({
227
256
  title: "",
228
257
  content: "",
229
- image: ""
258
+ image: "",
259
+ isForbidden: false
230
260
  })
231
261
 
232
262
  const isOtp = computed(() => IS_OTP.toLowerCase() === "y")
@@ -252,6 +282,7 @@ export default defineComponent({
252
282
  localStorage.setItem("emailResetPassword", email)
253
283
  infoDialog.value = {
254
284
  canGoNext: true,
285
+ isForbidden: false,
255
286
  title: t("auth.mailVerificationTitle"),
256
287
  content:
257
288
  IS_OTP.toLowerCase() === "y"
@@ -267,13 +298,15 @@ export default defineComponent({
267
298
  // NOTE: 403 is returned when the user is not allowed to reset the password
268
299
  infoDialog.value = {
269
300
  canGoNext: false,
270
- title: t("auth.forbidden.title"),
271
- content: t("auth.forbidden.subtitle"),
272
- image: "userNotFound"
301
+ isForbidden: true,
302
+ title: t("auth.restrictedTitle"),
303
+ content: t("auth.restrictedDescription"),
304
+ image: "restricted"
273
305
  }
274
306
  } else {
275
307
  infoDialog.value = {
276
308
  canGoNext: false,
309
+ isForbidden: false,
277
310
  title: t("auth.userNotFoundTitle"),
278
311
  content: t("auth.userNotFoundSubtitle"),
279
312
  image: "userNotFound"
@@ -331,6 +364,34 @@ export default defineComponent({
331
364
  </script>
332
365
 
333
366
  <style scoped>
367
+ .forbidden-sso-label {
368
+ font-size: 15px;
369
+ font-weight: 600;
370
+ color: #112240;
371
+ margin-bottom: 12px;
372
+ }
373
+
374
+ .forbidden-sso-button {
375
+ width: 260px;
376
+ max-width: 100%;
377
+ height: 36px;
378
+ border-radius: 8.85px;
379
+ background: #ebeeef;
380
+ font-size: 15px;
381
+ font-weight: 600;
382
+ color: #112240;
383
+ }
384
+
385
+ .forbidden-sso-button:hover {
386
+ background: #e0e5e7;
387
+ }
388
+
389
+ .forbidden-sso-icon {
390
+ width: 18px;
391
+ height: 18px;
392
+ margin-right: 8px;
393
+ }
394
+
334
395
  .center {
335
396
  margin-top: -115px;
336
397
  }
@@ -14,9 +14,17 @@
14
14
  <template v-if="restricted">
15
15
  <div class="forbidden-content" data-testid="magic-link-forbidden">
16
16
  <div class="warning-icon-outer">
17
- <svg width="32" height="30" viewBox="0 0 32 30" fill="none">
17
+ <svg
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ width="82"
20
+ height="82"
21
+ viewBox="0 0 82 82"
22
+ fill="none">
18
23
  <path
19
- d="M31.33 26.0414L18.0152 1.31484C17.0714 -0.438281 14.5574 -0.438281 13.6128 1.31484L0.298773 26.0414C0.0938612 26.422 -0.00887221 26.8492 0.000600544 27.2814C0.0100733 27.7135 0.131429 28.1358 0.352821 28.507C0.574214 28.8783 0.888079 29.1858 1.26378 29.3995C1.63948 29.6133 2.06418 29.7259 2.49643 29.7266H29.1285C29.561 29.7266 29.9862 29.6144 30.3625 29.401C30.7387 29.1875 31.0531 28.8801 31.275 28.5087C31.4968 28.1374 31.6185 27.7148 31.6282 27.2823C31.6378 26.8499 31.5351 26.4223 31.33 26.0414V26.0414ZM15.8144 25.8984C15.5054 25.8984 15.2033 25.8068 14.9463 25.6351C14.6894 25.4634 14.4891 25.2194 14.3708 24.9339C14.2526 24.6484 14.2216 24.3342 14.2819 24.0311C14.3422 23.728 14.491 23.4496 14.7095 23.2311C14.9281 23.0126 15.2065 22.8638 15.5096 22.8035C15.8127 22.7432 16.1268 22.7741 16.4123 22.8924C16.6978 23.0106 16.9419 23.2109 17.1136 23.4679C17.2853 23.7248 17.3769 24.0269 17.3769 24.3359C17.3769 24.5411 17.3365 24.7443 17.258 24.9339C17.1794 25.1235 17.0643 25.2957 16.9193 25.4408C16.7742 25.5859 16.6019 25.701 16.4123 25.7795C16.2228 25.858 16.0196 25.8984 15.8144 25.8984ZM17.5113 10.1836L17.0628 19.7148C17.0628 20.0464 16.9311 20.3643 16.6967 20.5987C16.4623 20.8331 16.1444 20.9648 15.8128 20.9648C15.4813 20.9648 15.1634 20.8331 14.929 20.5987C14.6945 20.3643 14.5628 20.0464 14.5628 19.7148L14.1144 10.1875C14.1043 9.95983 14.1402 9.73247 14.2198 9.51896C14.2995 9.30545 14.4213 9.11017 14.5781 8.94475C14.7348 8.77933 14.9233 8.64716 15.1322 8.55612C15.3411 8.46508 15.5662 8.41703 15.7941 8.41484H15.8105C16.0399 8.41473 16.267 8.46109 16.478 8.55114C16.689 8.6412 16.8796 8.77307 17.0383 8.9388C17.1969 9.10454 17.3204 9.3007 17.4011 9.51545C17.4819 9.73019 17.5183 9.95908 17.5081 10.1883L17.5113 10.1836Z"
24
+ d="M20.467 13.2638C20.467 13.2638 32.783 22.7041 51.5063 5.05236C68.1346 -10.6241 81.7117 14.1097 81.8067 25.9544C81.9298 41.298 65.1784 53.5748 73.3078 63.6526C81.4372 73.7304 57.1861 90.3722 44.116 75.9698C27.8572 58.0538 23.4525 72.6106 14.185 72.6106C7.53374 72.6106 -6.12216 55.9168 3.09953 43.4971C10.8594 33.0461 6.6273 29.5776 4.57785 25.9544C1.62159 20.7288 8.64242 6.54534 20.467 13.2638Z"
25
+ fill="#FFEFCF" />
26
+ <path
27
+ d="M63.1242 56.9728L44.1383 21.3586C42.7926 18.8336 39.2078 18.8336 37.861 21.3586L18.8762 56.9728C18.584 57.5209 18.4375 58.1363 18.4511 58.7587C18.4646 59.3811 18.6376 59.9894 18.9533 60.5241C19.269 61.0588 19.7165 61.5017 20.2522 61.8095C20.788 62.1174 21.3935 62.2797 22.0099 62.2806H59.9849C60.6017 62.2807 61.208 62.1191 61.7445 61.8116C62.281 61.5042 62.7293 61.0614 63.0457 60.5265C63.362 59.9916 63.5355 59.383 63.5493 58.7601C63.5631 58.1372 63.4166 57.5214 63.1242 56.9728V56.9728ZM41.0002 56.7669C40.5595 56.7669 40.1288 56.6349 39.7624 56.3876C39.396 56.1403 39.1104 55.7888 38.9418 55.3776C38.7732 54.9664 38.729 54.5139 38.815 54.0773C38.901 53.6408 39.1132 53.2397 39.4248 52.925C39.7364 52.6103 40.1333 52.3959 40.5655 52.3091C40.9977 52.2223 41.4457 52.2668 41.8528 52.4372C42.2599 52.6075 42.6079 52.896 42.8527 53.266C43.0975 53.6361 43.2282 54.0712 43.2282 54.5164C43.2282 54.8119 43.1706 55.1045 43.0586 55.3776C42.9466 55.6506 42.7825 55.8987 42.5756 56.1077C42.3687 56.3167 42.1231 56.4824 41.8528 56.5955C41.5825 56.7086 41.2928 56.7669 41.0002 56.7669ZM43.4198 34.1325L42.7804 47.8605C42.7804 48.338 42.5926 48.7959 42.2583 49.1336C41.924 49.4712 41.4707 49.6609 40.998 49.6609C40.5252 49.6609 40.0719 49.4712 39.7376 49.1336C39.4034 48.7959 39.2156 48.338 39.2156 47.8605L38.5761 34.1381C38.5618 33.8102 38.6129 33.4827 38.7265 33.1752C38.8401 32.8677 39.0138 32.5864 39.2373 32.3481C39.4608 32.1099 39.7295 31.9195 40.0274 31.7884C40.3253 31.6573 40.6463 31.5881 40.9712 31.5849H40.9946C41.3218 31.5847 41.6456 31.6515 41.9465 31.7812C42.2474 31.9109 42.5191 32.1009 42.7453 32.3396C42.9716 32.5783 43.1476 32.8608 43.2627 33.1701C43.3779 33.4794 43.4298 33.8091 43.4153 34.1392L43.4198 34.1325Z"
20
28
  fill="#FFBD3D" />
21
29
  </svg>
22
30
  </div>
@@ -28,6 +36,25 @@
28
36
  }}<strong>{{ forbiddenMessageParts.bold }}</strong
29
37
  >{{ forbiddenMessageParts.after }}
30
38
  </p>
39
+
40
+ <div class="forbidden-sso-section">
41
+ <p class="forbidden-sso-label">{{ $t("auth.connectWith") }}</p>
42
+ <q-btn
43
+ data-testid="forbidden-sso-btn"
44
+ data-cy="submit-sso-login"
45
+ unelevated
46
+ no-caps
47
+ dense
48
+ :loading="msalLoading"
49
+ :disable="msalLoading"
50
+ class="forbidden-sso-button"
51
+ @click="msalAuth">
52
+ <img
53
+ src="../../assets/logo-microsoft.svg"
54
+ class="forbidden-sso-icon" />
55
+ {{ $t("auth.microsoft") }}
56
+ </q-btn>
57
+ </div>
31
58
  </div>
32
59
 
33
60
  <a
@@ -293,6 +320,7 @@ import {
293
320
  } from "@quasar/extras/ionicons-v5"
294
321
 
295
322
  import { useMagicLink } from "composables/useMagicLink"
323
+ import { useAuth } from "composables/auth"
296
324
 
297
325
  type Channel = "email" | "sms"
298
326
 
@@ -328,7 +356,6 @@ export default defineComponent({
328
356
  isLoading,
329
357
  resendCountdown,
330
358
  restricted,
331
- restrictedMessage,
332
359
  isActivateContext,
333
360
  maskedEmail,
334
361
  requestChannels,
@@ -338,6 +365,8 @@ export default defineComponent({
338
365
  goBackToLogin
339
366
  } = useMagicLink()
340
367
 
368
+ const { msalLoading, msalAuth } = useAuth()
369
+
341
370
  const icons = {
342
371
  ionArrowBackOutline,
343
372
  ionCheckmarkCircle,
@@ -410,7 +439,7 @@ export default defineComponent({
410
439
  )
411
440
 
412
441
  const forbiddenMessageParts = computed<ForbiddenMessageParts>(() => {
413
- const message = restrictedMessage.value || t("auth.restrictedDescription")
442
+ const message = t("auth.restrictedDescription")
414
443
  const keywordIndex = message.indexOf(FORBIDDEN_MESSAGE_BOLD_KEYWORD)
415
444
  if (keywordIndex === -1) {
416
445
  return { before: message, bold: "", after: "" }
@@ -433,6 +462,8 @@ export default defineComponent({
433
462
  isLoading,
434
463
  resendCountdown,
435
464
  restricted,
465
+ msalLoading,
466
+ msalAuth,
436
467
  icons,
437
468
  stepTitle,
438
469
  stepDescription,
@@ -809,10 +840,8 @@ export default defineComponent({
809
840
  }
810
841
 
811
842
  .warning-icon-outer {
812
- width: 96px;
813
- height: 96px;
814
- border-radius: 9999px;
815
- background: rgba(255, 189, 61, 0.27);
843
+ width: 82px;
844
+ height: 82px;
816
845
  display: flex;
817
846
  align-items: center;
818
847
  justify-content: center;
@@ -837,6 +866,43 @@ export default defineComponent({
837
866
  text-align: center;
838
867
  }
839
868
 
869
+ .forbidden-sso-section {
870
+ width: 100%;
871
+ margin-top: 8px;
872
+ text-align: left;
873
+ }
874
+
875
+ .forbidden-sso-label {
876
+ font-size: 15px;
877
+ font-weight: 600;
878
+ line-height: 1.5em;
879
+ color: #001d3d;
880
+ margin: 0 0 12px 0;
881
+ }
882
+
883
+ .forbidden-sso-button {
884
+ display: flex;
885
+ width: 260px;
886
+ max-width: 100%;
887
+ height: 36px;
888
+ margin: 0 auto;
889
+ border-radius: 8.85px;
890
+ background: #ebeeef;
891
+ font-size: 15px;
892
+ font-weight: 600;
893
+ color: #112240;
894
+ }
895
+
896
+ .forbidden-sso-button:hover {
897
+ background: #e0e5e7;
898
+ }
899
+
900
+ .forbidden-sso-icon {
901
+ width: 18px;
902
+ height: 18px;
903
+ margin-right: 8px;
904
+ }
905
+
840
906
  @media screen and (max-width: 768px) {
841
907
  .login-card {
842
908
  padding: 32px 24px;
@@ -49,6 +49,11 @@ export default {
49
49
  back: "Back",
50
50
  backToLoginFull: "Back to login",
51
51
  continueWith: "Or continue with",
52
+ connectWith: "Sign in with",
53
+ microsoft: "Microsoft",
54
+ restrictedTitle: "Reset unavailable",
55
+ restrictedDescription:
56
+ "Your account uses Microsoft login. You are not allowed to manage your password. To change it, contact your administrator.",
52
57
  emailOrPasswordIncorrect: "Email or password incorrect",
53
58
  magicLinkCheckSmsMessage: "Check your text messages",
54
59
  magicLinkWarningSms:
@@ -52,6 +52,11 @@ export default {
52
52
  back: "Retour",
53
53
  backToLoginFull: "Retour à la connexion",
54
54
  continueWith: "Ou continuer avec",
55
+ connectWith: "Se connecter avec",
56
+ microsoft: "Microsoft",
57
+ restrictedTitle: "Réinitialisation impossible",
58
+ restrictedDescription:
59
+ "Votre compte utilise la connexion Microsoft. La gestion de votre mot de passe vous est interdite. Pour le modifier, contactez votre administrateur.",
55
60
  emailOrPasswordIncorrect: "Email ou mot de passe incorrect",
56
61
  magicLinkCheckSmsMessage: "Vérifiez vos messages",
57
62
  magicLinkWarningSms: