@drax/identity-vue 3.65.0 → 3.67.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/README.md CHANGED
@@ -72,6 +72,137 @@ Focused tests:
72
72
  npm exec --workspace @drax/identity-vue -- vitest run test/auth-session.spec.ts test/auth-session.integration.spec.ts test/components/__tests__/IdentityLogin.spec.ts
73
73
  ```
74
74
 
75
+ ## Registro con creación de tenant (opcional, REST)
76
+
77
+ En el **backend**:
78
+
79
+ ```dotenv
80
+ DRAX_REGISTRATION_TENANT_ENABLED=true
81
+ DRAX_DEFAULT_ROLE=Member
82
+ ```
83
+
84
+ Cargá `LoadIdentityConfigFromEnv()`, registrá `UserRoutes` y creá previamente el rol
85
+ configurado. Sin la flag, o con `false`, el registro no crea tenants. No se necesita
86
+ una variable Vite: `IdentityRegistration`, usado por `RegistrationPage`, consulta
87
+ `GET /api/auth/registration-options` → `{tenantRequired: boolean}`. Mientras no pueda
88
+ cargar estas opciones, bloquea el envío y permite reintentar.
89
+
90
+ Si está activo, el formulario exige un nombre de organización y envía
91
+ `tenantName` en `POST /api/users/register`. El backend crea un tenant **nuevo** y
92
+ asigna el usuario a ese tenant; rechaza nombres existentes con HTTP 422,
93
+ `tenantName: validation.unique`. No vincula visitantes a tenants existentes. Los
94
+ nombres se recortan y la unicidad es exacta y sensible a mayúsculas, igual que el
95
+ índice MongoDB existente. SQLite también aplica un índice único, incluyendo bases
96
+ ya creadas; si hay duplicados previos, hay que resolverlos manualmente antes de
97
+ inicializar. No se borran ni renombran datos automáticamente.
98
+
99
+ Para formularios propios, usá `useAuth().registrationOptions()` y
100
+ `useAuth().register({...datos, tenantName})`. El rol y el ID de tenant los determina
101
+ el servidor; enviar `role`, `tenant` o `active` no los sobreescribe. El usuario
102
+ continúa inactivo hasta la verificación existente por email/teléfono; no hay
103
+ login automático. Si falla la creación del usuario antes de persistir, se elimina
104
+ el tenant recién creado; si ya se persistió o falla después el email, se conserva
105
+ la asociación. No es una transacción distribuida entre bases/servicios.
106
+
107
+ Desplegá backend y frontend compatibles: el formulario actualizado requiere el
108
+ nuevo endpoint incluso cuando la flag está apagada. El registro GraphQL no está
109
+ implementado; su provider devuelve `tenantRequired: false` para las opciones.
110
+
111
+ ## Verificación de email y reenvío (REST)
112
+
113
+ `register(form)` conserva `{success, message}` y ahora devuelve el tipo público
114
+ `IRegistrationResponse` de `@drax/identity-front`, con
115
+ `verificationEmailSent?: boolean`. `true` indica aceptación por SMTP, **no entrega**;
116
+ `false` indica que la cuenta se creó pero el correo quedó pendiente. La ausencia
117
+ mantiene el comportamiento anterior. Un fallo SMTP ya no implica un error de
118
+ registro ni habilita la cuenta: no hay login automático.
119
+
120
+ `IdentityRegistration` muestra una advertencia si el valor es `false` y mantiene
121
+ el mensaje de éxito existente para `true` o ausente. Ofrece un botón de acceso al
122
+ reenvío (`auth.verificationResendTitle`), con el email prellenado, tanto desde el
123
+ formulario como después del éxito/advertencia o de un error (duplicado o red).
124
+ Al abrirlo, el reenvío **reemplaza** el formulario o el resultado del registro y
125
+ oculta el botón de acceso. «Volver al registro» (`auth.backToRegistration`) restaura
126
+ el estado anterior, conservando inputs, email, tenant y resultado de éxito/advertencia.
127
+ No es necesario crear otra cuenta ni agregar rutas.
128
+
129
+ El acceso al reenvío está **solo en registro**, separado del formulario principal
130
+ con un margen superior. `IdentityLogin` conserva los enlaces de registro y de
131
+ recuperación de contraseña según sus props, pero no muestra el acceso a verificación.
132
+ El reenvío solicita email explícitamente. Hay un único formulario visible, sin
133
+ formularios simultáneos ni anidados, y un único título de reenvío:
134
+ `auth.verificationFormTitle` («Account verification» / «Verificación de cuenta»).
135
+
136
+ El componente reutilizable está exportado:
137
+
138
+ ```vue
139
+ <script setup lang="ts">
140
+ import {IdentityVerificationResend} from '@drax/identity-vue'
141
+ </script>
142
+
143
+ <template>
144
+ <IdentityVerificationResend email="persona@example.com" />
145
+ </template>
146
+ ```
147
+
148
+ La prop `email` es opcional y editable. La prop opcional `backLabel?: string`
149
+ muestra un botón de vuelta de tipo `button` que emite `back`; está deshabilitado
150
+ mientras se procesa la solicitud. El componente padre decide qué restaurar al
151
+ recibir ese evento. Sin `backLabel`, el uso standalone conserva un único botón de
152
+ envío. El botón submit queda alineado a la derecha mediante un `v-spacer` anterior.
153
+ Formularios propios pueden usar `useAuth().resendVerificationEmail(email)` o
154
+ `AuthSystem.resendVerificationEmail(email)`.
155
+ REST hace `POST /api/users/verification/resend` con `{email}`. Tanto este endpoint
156
+ como el registro son anónimos: no envían `Authorization`, no disparan el preflight
157
+ de refresh y no borran las cabeceras del cliente compartido.
158
+
159
+ El resultado de reenvío es siempre neutral para cuentas inexistentes, verificadas,
160
+ limitadas o con fallo SMTP: `{success: true, message: 'If an account is pending
161
+ verification, activation instructions will be sent.'}`. La UI usa traducciones
162
+ es/en neutrales, sin confirmar existencia de la cuenta ni envío o entrega del
163
+ correo. Valida el email, muestra errores de red/`ClientError` y bloquea el envío
164
+ durante la solicitud. No impone una cuenta regresiva fija: el cooldown lo decide
165
+ el servidor (60 segundos por defecto, configurable). GraphQL rechaza esta operación
166
+ explícitamente como no implementada; no se crea una query ni mutación ficticia.
167
+ Los providers personalizados anteriores pueden omitir el método opcional de
168
+ `IAuthProvider`; sus APIs previas siguen funcionando y `AuthSystem` rechaza el
169
+ reenvío con un error explícito si el provider no lo soporta.
170
+
171
+ ### Backend: vencimiento, límites y recuperación de cuentas anteriores
172
+
173
+ Configuración opcional, cargada con `LoadIdentityConfigFromEnv()`:
174
+
175
+ ```dotenv
176
+ DRAX_EMAIL_VERIFICATION_TTL_SECONDS=86400
177
+ DRAX_EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS=60
178
+ ```
179
+
180
+ Ambos valores deben ser segundos enteros positivos. El código es de un solo uso y
181
+ vence a las 24 horas por defecto. Reenviar reutiliza un código vigente sin extender
182
+ su vencimiento; rota el código únicamente si venció o si el registro anterior no
183
+ tenía vencimiento. Los enlaces antiguos sin vencimiento ya no activan cuentas:
184
+ para recuperarlas, solicitá un reenvío con el email original desde login/registro.
185
+ No hace falta borrar ni volver a crear usuario o tenant. Solo son elegibles cuentas
186
+ con `origin: 'Registry'` y email sin verificar; el reenvío no altera sus datos ni
187
+ reactiva cuentas verificadas que hayan sido desactivadas administrativamente.
188
+
189
+ El cooldown por email se reclama atómicamente en la base de datos, también cuando
190
+ SMTP falla. Se admiten 10 intentos de reenvío por IP cada 15 minutos por proceso,
191
+ con memoria acotada; incluso los emails inexistentes consumen ese límite. En un
192
+ despliegue multiinstancia, agregá un rate limiter compartido/gateway y configurá
193
+ correctamente la IP de cliente detrás del proxy. No hay cola ni reintentos
194
+ automáticos de correo: el usuario solicita el reenvío. La persistencia SQLite se
195
+ actualiza de forma aditiva al inicializar el repositorio; no se borran registros.
196
+
197
+ Actualizá backend, `identity-share`, `identity-front` e `identity-vue` de forma
198
+ coherente y desplegá backend antes de la UI nueva. En el monorepo compilá primero
199
+ `identity-share` para que los tipos utilizados por los demás paquetes estén al día.
200
+
201
+ Pruebas enfocadas: `test/verification-resend.spec.ts`, `test/registration.spec.ts`,
202
+ `test/components/__tests__/IdentityLogin.spec.ts`,
203
+ `test/auth-session.integration.spec.ts` y
204
+ `../identity-front/test/verification-resend.test.ts`.
205
+
75
206
  ## Recommended IDE Setup
76
207
 
77
208
  [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.65.0",
6
+ "version": "3.67.0",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "module": "./src/index.ts",
@@ -22,9 +22,9 @@
22
22
  "@drax/common-vue": "^3.65.0",
23
23
  "@drax/crud-front": "^3.58.0",
24
24
  "@drax/crud-share": "^3.65.0",
25
- "@drax/crud-vue": "^3.65.0",
26
- "@drax/identity-front": "^3.65.0",
27
- "@drax/identity-share": "^3.65.0"
25
+ "@drax/crud-vue": "^3.66.0",
26
+ "@drax/identity-front": "^3.67.0",
27
+ "@drax/identity-share": "^3.67.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "pinia": "^3.0.4",
@@ -49,5 +49,5 @@
49
49
  "vue-tsc": "^3.2.4",
50
50
  "vuetify": "^3.11.8"
51
51
  },
52
- "gitHead": "6e6b1827de6d9f0449d6381f5054fe3e8f00c2ef"
52
+ "gitHead": "ffe6431614727caa3940fcb03da690d50c5032db"
53
53
  }
@@ -4,6 +4,7 @@ import {useAuth} from '../../composables/useAuth.js'
4
4
  import IdentityProfileView from "../IdentityProfileView/IdentityProfileView.vue";
5
5
  import {useI18n} from "vue-i18n";
6
6
  import IdentityRememberMe from '../IdentityRememberMe.vue'
7
+
7
8
  import {isRememberMeEnabled} from '../../composables/setupAuthSession'
8
9
  const {t, te} = useI18n()
9
10
 
@@ -22,6 +23,7 @@ const authError = ref('')
22
23
  const loading = ref(false)
23
24
  const rememberMe = ref(false)
24
25
 
26
+
25
27
  const isFormValid = computed(() => username.value.trim() !== '' && password.value.trim() !== '')
26
28
 
27
29
  const emit = defineEmits(['loginSuccess'])
@@ -60,6 +62,7 @@ function togglePasswordVisibility() {
60
62
  </v-card>
61
63
  </template>
62
64
 
65
+
63
66
  <template v-else>
64
67
 
65
68
  <v-form @submit.prevent="submitLogin">
@@ -124,6 +127,7 @@ function togglePasswordVisibility() {
124
127
  </v-card>
125
128
  </v-form>
126
129
 
130
+
127
131
  </template>
128
132
  </template>
129
133
 
@@ -1,18 +1,42 @@
1
1
  <script setup lang="ts">
2
- import {ref} from 'vue'
2
+ import {onMounted, ref} from 'vue'
3
+ import type {IRegistrationOptions} from '@drax/identity-front'
3
4
  import {useAuth} from "../../composables/useAuth.js";
4
5
  import {ClientError} from "@drax/common-front";
5
6
  import type {IClientInputError} from "@drax/common-front";
6
7
  import {useI18nValidation} from "@drax/common-vue";
7
8
  import {useI18n} from "vue-i18n";
8
9
  import {useRouter} from "vue-router"
10
+ import IdentityVerificationResend from '../IdentityVerificationResend/IdentityVerificationResend.vue'
9
11
 
10
12
  const {t,te} = useI18n()
11
13
  const {$ta} = useI18nValidation()
12
14
 
13
15
  const router = useRouter()
14
16
 
15
- const {register} = useAuth()
17
+ const {register, registrationOptions} = useAuth()
18
+ const options = ref<IRegistrationOptions | null>(null)
19
+ const optionsLoading = ref(true)
20
+ const optionsError = ref(false)
21
+ const tenantName = ref('')
22
+ const rform = ref<{validate: () => Promise<{valid: boolean}>} | null>(null)
23
+
24
+ async function loadRegistrationOptions() {
25
+ optionsLoading.value = true
26
+ optionsError.value = false
27
+ options.value = null
28
+ try {
29
+ const result = await registrationOptions()
30
+ if (typeof result?.tenantRequired !== 'boolean') throw new Error('Invalid registration options')
31
+ options.value = result
32
+ } catch {
33
+ optionsError.value = true
34
+ } finally {
35
+ optionsLoading.value = false
36
+ }
37
+ }
38
+
39
+ onMounted(loadRegistrationOptions)
16
40
 
17
41
  const variant = ref<"outlined" | "plain" | "filled" | "underlined" | "solo" | "solo-inverted" | "solo-filled" | undefined>('filled')
18
42
 
@@ -29,6 +53,8 @@ const inputErrors = ref<IClientInputError|undefined>({currentPassword: [], newPa
29
53
  const errorMsg = ref('')
30
54
  const loading = ref(false)
31
55
  const success = ref(false)
56
+ const verificationEmailSent = ref<boolean>()
57
+ const showVerificationResend = ref(false)
32
58
 
33
59
  let passwordVisibility = ref(false)
34
60
  let confirmPasswordVisibility = ref(false)
@@ -39,10 +65,23 @@ function confirmPasswordRule(value: string) {
39
65
  }
40
66
 
41
67
  async function submitRegistration() {
68
+ if (!options.value || optionsLoading.value || loading.value) return
69
+ loading.value = true
70
+ errorMsg.value = ''
71
+ inputErrors.value = undefined
42
72
  try {
43
- loading.value = true
44
- const result = await register(form.value)
73
+ if (options.value.tenantRequired && !tenantName.value.trim()) {
74
+ inputErrors.value = {tenantName: ['validation.required']}
75
+ return
76
+ }
77
+ if (!(await rform.value?.validate())?.valid) return
78
+ const payload = {
79
+ ...form.value,
80
+ ...(options.value.tenantRequired ? {tenantName: tenantName.value.trim()} : {})
81
+ }
82
+ const result = await register(payload)
45
83
  success.value = result.success
84
+ verificationEmailSent.value = result.verificationEmailSent
46
85
  } catch (err) {
47
86
  if (err instanceof ClientError) {
48
87
  inputErrors.value = err.inputErrors
@@ -61,12 +100,18 @@ async function submitRegistration() {
61
100
 
62
101
  <template>
63
102
  <v-sheet>
103
+ <identity-verification-resend
104
+ v-if="showVerificationResend"
105
+ :email="form.email"
106
+ :back-label="t('auth.backToRegistration')"
107
+ @back="showVerificationResend = false"
108
+ />
64
109
 
65
- <template v-if="success">
110
+ <template v-else-if="success">
66
111
  <v-card>
67
112
  <v-card-text>
68
- <v-alert type="success">
69
- {{ t('user.events.registrationComplete') }}
113
+ <v-alert :type="verificationEmailSent === false ? 'warning' : 'success'">
114
+ {{ verificationEmailSent === false ? t('auth.registrationVerificationPending') : t('user.events.registrationComplete') }}
70
115
  </v-alert>
71
116
  </v-card-text>
72
117
  <v-card-text class="text-center">
@@ -81,12 +126,29 @@ async function submitRegistration() {
81
126
  <v-form ref="rform" @submit.prevent="submitRegistration">
82
127
  <v-card variant="elevated">
83
128
  <v-card-title class="pa-4">{{ t('user.action.register') }}</v-card-title>
129
+ <v-card-text v-if="optionsLoading">
130
+ <v-alert type="info">{{ t('auth.registrationOptionsLoading') }}</v-alert>
131
+ </v-card-text>
132
+ <v-card-text v-if="optionsError">
133
+ <v-alert type="error">{{ t('auth.registrationOptionsError') }}</v-alert>
134
+ <v-btn @click="loadRegistrationOptions">{{ t('auth.registrationOptionsRetry') }}</v-btn>
135
+ </v-card-text>
84
136
  <v-card-text v-if="errorMsg">
85
137
  <v-alert type="error">
86
138
  {{ te(errorMsg) ?t(errorMsg) : errorMsg }}
87
139
  </v-alert>
88
140
  </v-card-text>
89
141
  <v-card-text>
142
+ <v-text-field
143
+ v-if="options?.tenantRequired"
144
+ id="tenant-name-input"
145
+ v-model="tenantName"
146
+ :label="t('auth.registrationTenantName')"
147
+ :variant="variant"
148
+ required
149
+ :rules="[(v: string) => !!v?.trim() || t('validation.required')]"
150
+ :error-messages="$ta(inputErrors?.tenantName)"
151
+ ></v-text-field>
90
152
  <v-text-field
91
153
  id="name-input"
92
154
  :label="t('user.field.name')"
@@ -167,7 +229,7 @@ async function submitRegistration() {
167
229
  color="grey"
168
230
  variant="text"
169
231
  id="submit-button"
170
- type="submit"
232
+ type="button"
171
233
  href="/login"
172
234
  >
173
235
  {{ t('auth.login') }}
@@ -179,6 +241,7 @@ async function submitRegistration() {
179
241
  id="submit-button"
180
242
  type="submit"
181
243
  :loading="loading"
244
+ :disabled="!options || optionsLoading || loading"
182
245
  >
183
246
  {{ t('action.sent') }}
184
247
  </v-btn>
@@ -186,7 +249,20 @@ async function submitRegistration() {
186
249
  </v-card>
187
250
  </v-form>
188
251
  </template>
252
+
253
+
189
254
  </v-sheet>
255
+
256
+ <div v-if="!showVerificationResend" class="mt-2 text-center">
257
+ <v-btn
258
+ type="button"
259
+ variant="text"
260
+ color="secondary"
261
+ @click="showVerificationResend = true"
262
+ >
263
+ {{ t('auth.verificationResendTitle') }}
264
+ </v-btn>
265
+ </div>
190
266
  </template>
191
267
 
192
268
  <style scoped lang="sass">
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import {ref, watch} from 'vue'
3
+ import {ClientError} from '@drax/common-front'
4
+ import type {IClientInputError} from '@drax/common-front'
5
+ import {useI18nValidation} from '@drax/common-vue'
6
+ import {useI18n} from 'vue-i18n'
7
+ import {useAuth} from '../../composables/useAuth.js'
8
+
9
+ const props = withDefaults(defineProps<{email?: string, backLabel?: string}>(), {email: ''})
10
+ const emit = defineEmits<{back: []}>()
11
+ const {t, te} = useI18n()
12
+ const {$ta} = useI18nValidation()
13
+ const {resendVerificationEmail} = useAuth()
14
+ const email = ref(props.email)
15
+ const loading = ref(false)
16
+ const success = ref(false)
17
+ const errorMsg = ref('')
18
+ const inputErrors = ref<IClientInputError>()
19
+
20
+ watch(() => props.email, value => { email.value = value })
21
+ watch(email, () => {
22
+ success.value = false
23
+ errorMsg.value = ''
24
+ inputErrors.value = undefined
25
+ })
26
+
27
+ async function submitResend() {
28
+ if (loading.value) return
29
+ success.value = false
30
+ errorMsg.value = ''
31
+ inputErrors.value = undefined
32
+ const address = email.value.trim()
33
+ if (!address || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(address)) {
34
+ inputErrors.value = {email: [address ? 'auth.verificationEmailInvalid' : 'validation.required']}
35
+ return
36
+ }
37
+ loading.value = true
38
+ try {
39
+ const result = await resendVerificationEmail(address)
40
+ if (!result.success) throw new Error('auth.verificationResendError')
41
+ // A neutral response does not confirm an account exists or that mail was sent.
42
+ success.value = true
43
+ } catch (error) {
44
+ if (error instanceof ClientError) inputErrors.value = error.inputErrors
45
+ errorMsg.value = error instanceof Error ? error.message : 'auth.verificationResendError'
46
+ } finally {
47
+ loading.value = false
48
+ }
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <v-form @submit.prevent="submitResend" novalidate>
54
+ <v-card variant="flat">
55
+ <v-card-title>{{ t('auth.verificationFormTitle') }}</v-card-title>
56
+ <v-card-text>
57
+ <v-alert v-if="success" type="success" role="status" class="mb-4">
58
+ {{ t('auth.verificationResendNeutral') }}
59
+ </v-alert>
60
+ <v-alert v-if="errorMsg" type="error" role="alert" class="mb-4">
61
+ {{ te(errorMsg) ? t(errorMsg) : errorMsg }}
62
+ </v-alert>
63
+ <v-text-field
64
+ v-model="email"
65
+ name="verification-email"
66
+ type="email"
67
+ autocomplete="email"
68
+ :label="t('user.field.email')"
69
+ prepend-inner-icon="mdi-email"
70
+ variant="outlined"
71
+ required
72
+ :disabled="loading"
73
+ :error-messages="$ta(inputErrors?.email)"
74
+ />
75
+ </v-card-text>
76
+ <v-card-actions>
77
+ <v-btn v-if="backLabel" type="button" variant="text" :disabled="loading" @click="emit('back')">
78
+ {{ backLabel }}
79
+ </v-btn>
80
+ <v-spacer />
81
+ <v-btn type="submit" color="primary" variant="tonal" :loading="loading" :disabled="loading">
82
+ {{ t('auth.verificationResend') }}
83
+ </v-btn>
84
+ </v-card-actions>
85
+ </v-card>
86
+ </v-form>
87
+ </template>
@@ -6,7 +6,7 @@ import {useAuthStore} from "../../stores/AuthStore"
6
6
  import {useI18n} from "vue-i18n"
7
7
 
8
8
  const authStore = useAuthStore()
9
- const {switchTenant, hasPermission} = useAuth()
9
+ const {switchTenant, hasPermission, isAuthenticated} = useAuth()
10
10
 
11
11
  const {t} = useI18n()
12
12
  const tenant = ref();
@@ -19,7 +19,7 @@ async function changeTenant() {
19
19
  </script>
20
20
 
21
21
  <template>
22
- <v-toolbar v-if="hasPermission('user:switchTenant')">
22
+ <v-toolbar v-if="isAuthenticated() && hasPermission('user:switchTenant')">
23
23
  <v-toolbar-title>
24
24
  <b>{{t('tenant.current')}}:</b> {{authStore.authUser?.tenant ? authStore.authUser?.tenant?.name : '-'}}
25
25
  </v-toolbar-title>
@@ -162,7 +162,8 @@ export async function setupAuthSession(): Promise<void> {
162
162
  }
163
163
  syncAuthHeaders(store)
164
164
  client.setBeforeRequest?.(async (url) => {
165
- if (/\/api\/auth\/(login|refresh|logout)\/?(?:[?#]|$)/.test(url)) return
165
+ if (/\/api\/auth\/(login|refresh|logout|registration-options)\/?(?:[?#]|$)/.test(url)) return
166
+ if (/\/api\/users\/(register|verification\/resend)\/?(?:[?#]|$)/.test(url)) return
166
167
  // The profile fetch uses the fresh JWT and must not await its own refresh promise.
167
168
  if (fetchingUser.has(store) && /\/api\/auth\/me\/?(?:[?#]|$)/.test(url)) return
168
169
  await refreshAuthSession(store)
@@ -81,6 +81,14 @@ export function useAuth() {
81
81
  return await authSystem.recoveryPasswordComplete(recoveryCode, newPassword)
82
82
  }
83
83
 
84
+ const registrationOptions = async () => {
85
+ return await authSystem.registrationOptions()
86
+ }
87
+
88
+ const resendVerificationEmail = async (email: string) => {
89
+ return await authSystem.resendVerificationEmail(email)
90
+ }
91
+
84
92
  const register = async (form: any) => {
85
93
  return await authSystem.register(form)
86
94
  }
@@ -147,7 +155,7 @@ export function useAuth() {
147
155
  isAuthenticated, fetchAuthUser,
148
156
  changeOwnPassword, changeAvatar, passwordPolicy,
149
157
  recoveryPasswordRequest, recoveryPasswordComplete,
150
- register, switchTenant
158
+ register, registrationOptions, resendVerificationEmail, switchTenant
151
159
  }
152
160
 
153
161
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export {setupAuthSession, refreshAuthSession, isRememberMeEnabled} from './composables/setupAuthSession'
2
2
  export {default as IdentityRememberMe} from './components/IdentityRememberMe.vue'
3
+ export {default as IdentityVerificationResend} from './components/IdentityVerificationResend/IdentityVerificationResend.vue'
3
4
 
4
5
  import IdentityLogin from "./components/IdentityLogin/IdentityLogin.vue";
5
6
  import IdentityProfileAvatar from "./components/IdentityProfileAvatar/IdentityProfileAvatar.vue";