@drax/identity-vue 0.9.0 → 0.10.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 +8 -8
- package/src/components/IdentityChangeOwnPassword/IdentityChangeOwnPassword.vue +4 -4
- package/src/components/IdentityLogin/IdentityLogin.vue +21 -2
- package/src/components/IdentityRecoveryPasswordComplete/IdentityRecoveryPasswordComplete.vue +162 -0
- package/src/components/IdentityRecoveryPasswordRequest/IdentityRecoveryPasswordRequest.vue +109 -0
- package/src/components/IdentityRegistration/IdentityRegistration.vue +197 -0
- package/src/composables/useAuth.ts +20 -1
- package/src/index.ts +6 -2
- package/src/pages/LoginPage.vue +1 -1
- package/src/pages/PasswordRecoveryCompletePage.vue +23 -0
- package/src/pages/PasswordRecoveryRequestPage.vue +23 -0
- package/src/pages/ProfilePage.vue +1 -1
- package/src/pages/RegistrationPage.vue +22 -0
- package/src/routes/IdentityAuthRoutes.ts +60 -0
- package/src/routes/IdentityCrudRoutes.ts +53 -0
- package/src/routes/IdentityRoutes.ts +5 -73
- package/src/i18n/I18n.ts +0 -10
- package/src/i18n/I18nMessages.ts +0 -22
- /package/src/pages/{PasswordPage.vue → PasswordChangePage.vue} +0 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.10.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/common-front": "^0.
|
|
28
|
-
"@drax/common-vue": "^0.
|
|
29
|
-
"@drax/crud-front": "^0.
|
|
30
|
-
"@drax/crud-share": "^0.
|
|
31
|
-
"@drax/crud-vue": "^0.
|
|
32
|
-
"@drax/identity-share": "^0.
|
|
27
|
+
"@drax/common-front": "^0.10.0",
|
|
28
|
+
"@drax/common-vue": "^0.10.0",
|
|
29
|
+
"@drax/crud-front": "^0.10.0",
|
|
30
|
+
"@drax/crud-share": "^0.10.0",
|
|
31
|
+
"@drax/crud-vue": "^0.10.0",
|
|
32
|
+
"@drax/identity-share": "^0.10.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"pinia": "^2.2.2",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"vue-tsc": "^2.1.6",
|
|
67
67
|
"vuetify": "^3.7.1"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "bc1b8a35563ccffdf7719cfcc588f12df0e012bb"
|
|
70
70
|
}
|
|
@@ -31,7 +31,7 @@ function confirmPasswordRule(value: string) {
|
|
|
31
31
|
return newPassword.value.trim() === confirmPassword.value.trim() || t('validation.password.confirmed')
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async function
|
|
34
|
+
async function submitChangePassword() {
|
|
35
35
|
try {
|
|
36
36
|
loading.value = true
|
|
37
37
|
await changeOwnPassword(currentPassword.value.trim(), newPassword.value.trim())
|
|
@@ -57,15 +57,15 @@ async function submitChangePassowrd() {
|
|
|
57
57
|
|
|
58
58
|
<template v-if="changed">
|
|
59
59
|
<v-alert type="success">
|
|
60
|
-
{{ t('user.passwordChanged') }}
|
|
60
|
+
{{ t('user.events.passwordChanged') }}
|
|
61
61
|
</v-alert>
|
|
62
62
|
</template>
|
|
63
63
|
|
|
64
64
|
<template v-else>
|
|
65
65
|
|
|
66
|
-
<v-form @submit.prevent="
|
|
66
|
+
<v-form @submit.prevent="submitChangePassword">
|
|
67
67
|
<v-card variant="elevated">
|
|
68
|
-
<v-card-title class="pa-4
|
|
68
|
+
<v-card-title class="pa-4">{{ t('user.action.changeOwnPassword') }}</v-card-title>
|
|
69
69
|
<v-card-text v-if="errorMsg">
|
|
70
70
|
<v-alert type="error">
|
|
71
71
|
{{ te(errorMsg) ?t(errorMsg) : errorMsg }}
|
|
@@ -7,6 +7,13 @@ const {t, te} = useI18n()
|
|
|
7
7
|
|
|
8
8
|
const {login, isAuthenticated} = useAuth()
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
defineProps({
|
|
12
|
+
recovery: {type: Boolean, default: false},
|
|
13
|
+
register: {type: Boolean, default: false},
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
|
|
10
17
|
const username = ref('')
|
|
11
18
|
const password = ref('')
|
|
12
19
|
const authError = ref('')
|
|
@@ -51,7 +58,7 @@ function togglePasswordVisibility() {
|
|
|
51
58
|
<template v-else>
|
|
52
59
|
|
|
53
60
|
<v-form @submit.prevent="submitLogin">
|
|
54
|
-
<v-card variant="elevated" class="pa-6">
|
|
61
|
+
<v-card variant="elevated" class="pa-6 pb-0">
|
|
55
62
|
<v-card-title class="pa-4 text-center">{{ te('auth.signIn') ? t('auth.signIn') : 'Sign In' }}</v-card-title>
|
|
56
63
|
<v-card-text v-if="authError">
|
|
57
64
|
<v-alert type="error">
|
|
@@ -84,7 +91,7 @@ function togglePasswordVisibility() {
|
|
|
84
91
|
<v-card-actions>
|
|
85
92
|
<v-spacer></v-spacer>
|
|
86
93
|
<v-btn
|
|
87
|
-
class="mb-
|
|
94
|
+
class="mb-4"
|
|
88
95
|
color="blue"
|
|
89
96
|
size="large"
|
|
90
97
|
variant="tonal"
|
|
@@ -97,8 +104,20 @@ function togglePasswordVisibility() {
|
|
|
97
104
|
{{ te('auth.login') ? t('auth.login') : 'Login' }}
|
|
98
105
|
</v-btn>
|
|
99
106
|
</v-card-actions>
|
|
107
|
+
|
|
108
|
+
<v-divider></v-divider>
|
|
109
|
+
<v-card-actions v-if="recovery || register" class="pa-0 ma-0">
|
|
110
|
+
<v-btn v-if="register" size="small" color="grey" variant="text" href="/registration">
|
|
111
|
+
{{ te('auth.register') ? t('auth.register') : 'Registro' }}
|
|
112
|
+
</v-btn>
|
|
113
|
+
<v-spacer></v-spacer>
|
|
114
|
+
<v-btn v-if="recovery" size="small" color="grey" variant="text" href="/password/recovery/request">
|
|
115
|
+
{{ te('auth.recoveryPassword') ? t('auth.recoveryPassword') : 'Recovery Password' }}
|
|
116
|
+
</v-btn>
|
|
117
|
+
</v-card-actions>
|
|
100
118
|
</v-card>
|
|
101
119
|
</v-form>
|
|
120
|
+
|
|
102
121
|
</template>
|
|
103
122
|
</template>
|
|
104
123
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, ref, onMounted} from 'vue'
|
|
3
|
+
import {useAuth} from "../../composables/useAuth.js";
|
|
4
|
+
import {ClientError} from "@drax/common-front";
|
|
5
|
+
import type {IClientInputError} from "@drax/common-front";
|
|
6
|
+
import {useI18nValidation} from "@drax/common-vue";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
import {useRoute, useRouter} from "vue-router"
|
|
9
|
+
|
|
10
|
+
const {t,te} = useI18n()
|
|
11
|
+
const {$ta} = useI18nValidation()
|
|
12
|
+
|
|
13
|
+
const route = useRoute()
|
|
14
|
+
const router = useRouter()
|
|
15
|
+
|
|
16
|
+
const {recoveryPasswordComplete} = useAuth()
|
|
17
|
+
|
|
18
|
+
const recoveryCode = ref('')
|
|
19
|
+
const newPassword = ref('')
|
|
20
|
+
const confirmPassword = ref('')
|
|
21
|
+
const inputErrors = ref<IClientInputError|undefined>({currentPassword: [], newPassword: [], confirmPassword: []})
|
|
22
|
+
const errorMsg = ref('')
|
|
23
|
+
const loading = ref(false)
|
|
24
|
+
const success = ref(false)
|
|
25
|
+
|
|
26
|
+
let recoveryCodeVisibility = ref(false)
|
|
27
|
+
let newPasswordVisibility = ref(false)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const isFormValid = computed(() =>
|
|
31
|
+
recoveryCode.value.trim() !== '' && newPassword.value.trim() !== ''
|
|
32
|
+
&& newPassword.value.trim() === confirmPassword.value.trim()
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const recoveryCodeParam = computed(() => {
|
|
36
|
+
return route.params.code
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
if (recoveryCodeParam.value) {
|
|
41
|
+
recoveryCode.value = recoveryCodeParam.value as string
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
function confirmPasswordRule(value: string) {
|
|
46
|
+
return newPassword.value.trim() === confirmPassword.value.trim() || t('validation.password.confirmed')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function submitResetPassword() {
|
|
50
|
+
try {
|
|
51
|
+
loading.value = true
|
|
52
|
+
await recoveryPasswordComplete(recoveryCode.value.trim(), newPassword.value.trim())
|
|
53
|
+
success.value = true
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof ClientError) {
|
|
56
|
+
inputErrors.value = err.inputErrors
|
|
57
|
+
}if(err instanceof Error) {
|
|
58
|
+
errorMsg.value = err.message
|
|
59
|
+
}
|
|
60
|
+
const error = err as Error
|
|
61
|
+
errorMsg.value = error.message
|
|
62
|
+
} finally {
|
|
63
|
+
loading.value = false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<v-sheet>
|
|
72
|
+
|
|
73
|
+
<template v-if="success">
|
|
74
|
+
<v-card>
|
|
75
|
+
<v-card-text>
|
|
76
|
+
<v-alert type="success">
|
|
77
|
+
{{ t('user.passwordChanged') }}
|
|
78
|
+
</v-alert>
|
|
79
|
+
</v-card-text>
|
|
80
|
+
<v-card-text class="text-center">
|
|
81
|
+
<v-btn color="primary" @click="router.push('/login')">{{ t('user.action.login') }}</v-btn>
|
|
82
|
+
</v-card-text>
|
|
83
|
+
</v-card>
|
|
84
|
+
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<template v-else>
|
|
88
|
+
|
|
89
|
+
<v-form @submit.prevent="submitResetPassword">
|
|
90
|
+
<v-card variant="elevated">
|
|
91
|
+
<v-card-title class="pa-4">{{ t('user.action.recoveryPassword') }}</v-card-title>
|
|
92
|
+
<v-card-text v-if="errorMsg">
|
|
93
|
+
<v-alert type="error">
|
|
94
|
+
{{ te(errorMsg) ?t(errorMsg) : errorMsg }}
|
|
95
|
+
</v-alert>
|
|
96
|
+
</v-card-text>
|
|
97
|
+
<v-card-text>
|
|
98
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.recoveryCode') }}</div>
|
|
99
|
+
<v-text-field
|
|
100
|
+
variant="outlined"
|
|
101
|
+
id="recovery-code-input"
|
|
102
|
+
v-model="recoveryCode"
|
|
103
|
+
prepend-inner-icon="mdi-code-braces-box"
|
|
104
|
+
required
|
|
105
|
+
readonly
|
|
106
|
+
:error-messages="$ta(inputErrors?.recoveryCode)"
|
|
107
|
+
></v-text-field>
|
|
108
|
+
|
|
109
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.newPassword') }}</div>
|
|
110
|
+
<!-- NEW PASSWORD-->
|
|
111
|
+
<v-text-field
|
|
112
|
+
variant="outlined"
|
|
113
|
+
id="new-password-input"
|
|
114
|
+
v-model="newPassword"
|
|
115
|
+
:type="newPasswordVisibility ? 'text': 'password'"
|
|
116
|
+
required
|
|
117
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
118
|
+
:append-inner-icon="newPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
119
|
+
@click:append-inner="newPasswordVisibility = !newPasswordVisibility"
|
|
120
|
+
autocomplete="new-password"
|
|
121
|
+
:error-messages="$ta(inputErrors?.newPassword)"
|
|
122
|
+
></v-text-field>
|
|
123
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.confirmPassword') }}</div>
|
|
124
|
+
<!-- CONFIRM PASSWORD-->
|
|
125
|
+
<v-text-field
|
|
126
|
+
variant="outlined"
|
|
127
|
+
id="confirm-password-input"
|
|
128
|
+
v-model="confirmPassword"
|
|
129
|
+
:type="newPasswordVisibility ? 'text': 'password'"
|
|
130
|
+
required
|
|
131
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
132
|
+
:append-inner-icon="newPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
133
|
+
@click:append-inner="newPasswordVisibility = !newPasswordVisibility"
|
|
134
|
+
autocomplete="new-password"
|
|
135
|
+
:error-messages="$ta(inputErrors?.confirmPassword)"
|
|
136
|
+
:rules="[confirmPasswordRule]"
|
|
137
|
+
></v-text-field>
|
|
138
|
+
</v-card-text>
|
|
139
|
+
<v-card-actions>
|
|
140
|
+
<v-spacer></v-spacer>
|
|
141
|
+
<v-btn
|
|
142
|
+
class="mb-8"
|
|
143
|
+
color="blue"
|
|
144
|
+
size="large"
|
|
145
|
+
variant="tonal"
|
|
146
|
+
id="submit-button"
|
|
147
|
+
type="submit"
|
|
148
|
+
block
|
|
149
|
+
:disabled="!isFormValid"
|
|
150
|
+
>
|
|
151
|
+
{{ t('action.sent') }}
|
|
152
|
+
</v-btn>
|
|
153
|
+
</v-card-actions>
|
|
154
|
+
</v-card>
|
|
155
|
+
</v-form>
|
|
156
|
+
</template>
|
|
157
|
+
</v-sheet>
|
|
158
|
+
</template>
|
|
159
|
+
|
|
160
|
+
<style scoped lang="sass">
|
|
161
|
+
// Your styles here
|
|
162
|
+
</style>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, ref} from 'vue'
|
|
3
|
+
import {useAuth} from "../../composables/useAuth.js";
|
|
4
|
+
import {ClientError} from "@drax/common-front";
|
|
5
|
+
import type {IClientInputError} from "@drax/common-front";
|
|
6
|
+
import {useI18nValidation} from "@drax/common-vue";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
|
|
9
|
+
const {t,te} = useI18n()
|
|
10
|
+
const {$ta} = useI18nValidation()
|
|
11
|
+
|
|
12
|
+
const {recoveryPasswordRequest} = useAuth()
|
|
13
|
+
|
|
14
|
+
const email = ref('')
|
|
15
|
+
const inputErrors = ref<IClientInputError|undefined>({currentPassword: [], newPassword: [], confirmPassword: []})
|
|
16
|
+
const errorMsg = ref('')
|
|
17
|
+
const loading = ref(false)
|
|
18
|
+
const success = ref(false)
|
|
19
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
20
|
+
const emailRules = [(v:any) => emailRegex.test(v) || te('validation.email.invalid')]
|
|
21
|
+
|
|
22
|
+
const isFormValid = computed(() =>
|
|
23
|
+
email.value.trim() !== '' && emailRegex.test(email.value)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async function submitResetPassword() {
|
|
28
|
+
try {
|
|
29
|
+
loading.value = true
|
|
30
|
+
const r = await recoveryPasswordRequest(email.value.trim())
|
|
31
|
+
|
|
32
|
+
console.log(r)
|
|
33
|
+
|
|
34
|
+
success.value = true
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof ClientError) {
|
|
37
|
+
inputErrors.value = err.inputErrors
|
|
38
|
+
}if(err instanceof Error) {
|
|
39
|
+
errorMsg.value = err.message
|
|
40
|
+
}
|
|
41
|
+
const error = err as Error
|
|
42
|
+
errorMsg.value = error.message
|
|
43
|
+
} finally {
|
|
44
|
+
loading.value = false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<v-sheet>
|
|
53
|
+
|
|
54
|
+
<template v-if="success">
|
|
55
|
+
<v-alert type="success">
|
|
56
|
+
{{ t('user.events.recoveryPasswordInfo') }}
|
|
57
|
+
</v-alert>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<template v-else>
|
|
61
|
+
|
|
62
|
+
<v-form @submit.prevent="submitResetPassword">
|
|
63
|
+
<v-card variant="elevated">
|
|
64
|
+
<v-card-title class="pa-4">{{ t('user.action.recoveryPasswordRequest') }}</v-card-title>
|
|
65
|
+
<v-card-text v-if="errorMsg">
|
|
66
|
+
<v-alert type="error">
|
|
67
|
+
{{ te(errorMsg) ?t(errorMsg) : errorMsg }}
|
|
68
|
+
</v-alert>
|
|
69
|
+
</v-card-text>
|
|
70
|
+
<v-card-text>
|
|
71
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.email') }}</div>
|
|
72
|
+
<!-- USER EMAIL-->
|
|
73
|
+
<v-text-field
|
|
74
|
+
variant="outlined"
|
|
75
|
+
id="recovery-code-input"
|
|
76
|
+
v-model="email"
|
|
77
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
78
|
+
required
|
|
79
|
+
autocomplete="new-password"
|
|
80
|
+
:error-messages="$ta(inputErrors?.recoveryCode)"
|
|
81
|
+
:rules="emailRules"
|
|
82
|
+
></v-text-field>
|
|
83
|
+
|
|
84
|
+
</v-card-text>
|
|
85
|
+
<v-card-actions>
|
|
86
|
+
<v-spacer></v-spacer>
|
|
87
|
+
<v-btn
|
|
88
|
+
:loading="loading"
|
|
89
|
+
class="mb-8"
|
|
90
|
+
color="blue"
|
|
91
|
+
size="large"
|
|
92
|
+
variant="tonal"
|
|
93
|
+
id="submit-button"
|
|
94
|
+
type="submit"
|
|
95
|
+
block
|
|
96
|
+
:disabled="!isFormValid"
|
|
97
|
+
>
|
|
98
|
+
{{ t('action.sent') }}
|
|
99
|
+
</v-btn>
|
|
100
|
+
</v-card-actions>
|
|
101
|
+
</v-card>
|
|
102
|
+
</v-form>
|
|
103
|
+
</template>
|
|
104
|
+
</v-sheet>
|
|
105
|
+
</template>
|
|
106
|
+
|
|
107
|
+
<style scoped lang="sass">
|
|
108
|
+
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed, ref, onMounted} from 'vue'
|
|
3
|
+
import {useAuth} from "../../composables/useAuth.js";
|
|
4
|
+
import {ClientError} from "@drax/common-front";
|
|
5
|
+
import type {IClientInputError} from "@drax/common-front";
|
|
6
|
+
import {useI18nValidation} from "@drax/common-vue";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
import {useRoute, useRouter} from "vue-router"
|
|
9
|
+
|
|
10
|
+
const {t,te} = useI18n()
|
|
11
|
+
const {$ta} = useI18nValidation()
|
|
12
|
+
|
|
13
|
+
const route = useRoute()
|
|
14
|
+
const router = useRouter()
|
|
15
|
+
|
|
16
|
+
const {register} = useAuth()
|
|
17
|
+
|
|
18
|
+
const rform = ref()
|
|
19
|
+
|
|
20
|
+
const variant = ref('filled')
|
|
21
|
+
|
|
22
|
+
const form = ref({
|
|
23
|
+
username: '',
|
|
24
|
+
name: '',
|
|
25
|
+
email: '',
|
|
26
|
+
phone: '',
|
|
27
|
+
password: '',
|
|
28
|
+
confirmPassword: ''
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const inputErrors = ref<IClientInputError|undefined>({currentPassword: [], newPassword: [], confirmPassword: []})
|
|
32
|
+
const errorMsg = ref('')
|
|
33
|
+
const loading = ref(false)
|
|
34
|
+
const success = ref(false)
|
|
35
|
+
|
|
36
|
+
let passwordVisibility = ref(false)
|
|
37
|
+
let confirmPasswordVisibility = ref(false)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function confirmPasswordRule(value: string) {
|
|
41
|
+
return form.value.password.trim() === form.value.confirmPassword.trim() || t('validation.password.confirmed')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function submitRegistration() {
|
|
45
|
+
try {
|
|
46
|
+
loading.value = true
|
|
47
|
+
await register(form.value)
|
|
48
|
+
success.value = true
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err instanceof ClientError) {
|
|
51
|
+
inputErrors.value = err.inputErrors
|
|
52
|
+
}if(err instanceof Error) {
|
|
53
|
+
errorMsg.value = err.message
|
|
54
|
+
}
|
|
55
|
+
const error = err as Error
|
|
56
|
+
errorMsg.value = error.message
|
|
57
|
+
} finally {
|
|
58
|
+
loading.value = false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<v-sheet>
|
|
67
|
+
|
|
68
|
+
<template v-if="success">
|
|
69
|
+
<v-card>
|
|
70
|
+
<v-card-text>
|
|
71
|
+
<v-alert type="success">
|
|
72
|
+
{{ t('user.event.registrationComplete') }}
|
|
73
|
+
</v-alert>
|
|
74
|
+
</v-card-text>
|
|
75
|
+
<v-card-text class="text-center">
|
|
76
|
+
<v-btn color="primary" @click="router.push('/login')">{{ t('user.action.login') }}</v-btn>
|
|
77
|
+
</v-card-text>
|
|
78
|
+
</v-card>
|
|
79
|
+
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<template v-else>
|
|
83
|
+
|
|
84
|
+
<v-form ref="rform" @submit.prevent="submitRegistration">
|
|
85
|
+
<v-card variant="elevated">
|
|
86
|
+
<v-card-title class="pa-4">{{ t('user.action.register') }}</v-card-title>
|
|
87
|
+
<v-card-text v-if="errorMsg">
|
|
88
|
+
<v-alert type="error">
|
|
89
|
+
{{ te(errorMsg) ?t(errorMsg) : errorMsg }}
|
|
90
|
+
</v-alert>
|
|
91
|
+
</v-card-text>
|
|
92
|
+
<v-card-text>
|
|
93
|
+
<v-text-field
|
|
94
|
+
id="name-input"
|
|
95
|
+
:label="t('user.field.name')"
|
|
96
|
+
v-model="form.name"
|
|
97
|
+
prepend-inner-icon="mdi-card-account-details"
|
|
98
|
+
:variant="variant"
|
|
99
|
+
:rules="[v => !!v || t('validation.required')]"
|
|
100
|
+
:error-messages="$ta(inputErrors?.name)"
|
|
101
|
+
|
|
102
|
+
></v-text-field>
|
|
103
|
+
|
|
104
|
+
<v-text-field
|
|
105
|
+
id="username-input"
|
|
106
|
+
:label="t('user.field.username')"
|
|
107
|
+
v-model="form.username"
|
|
108
|
+
prepend-inner-icon="mdi-account-question"
|
|
109
|
+
:variant="variant"
|
|
110
|
+
:rules="[v => !!v || t('validation.required')]"
|
|
111
|
+
autocomplete="new-username"
|
|
112
|
+
:error-messages="$ta(inputErrors?.username)"
|
|
113
|
+
|
|
114
|
+
></v-text-field>
|
|
115
|
+
|
|
116
|
+
<v-text-field
|
|
117
|
+
v-model="form.email"
|
|
118
|
+
:variant="variant"
|
|
119
|
+
id="email-input"
|
|
120
|
+
:label="t('user.field.email')"
|
|
121
|
+
prepend-inner-icon="mdi-email"
|
|
122
|
+
:rules="[(v:any) => !!v || t('validation.required')]"
|
|
123
|
+
:error-messages="$ta(inputErrors?.email)"
|
|
124
|
+
></v-text-field>
|
|
125
|
+
|
|
126
|
+
<v-text-field
|
|
127
|
+
v-model="form.phone"
|
|
128
|
+
:variant="variant"
|
|
129
|
+
id="phone-input"
|
|
130
|
+
:label="t('user.field.phone')"
|
|
131
|
+
prepend-inner-icon="mdi-phone"
|
|
132
|
+
:rules="[(v:any) => !!v || t('validation.required')]"
|
|
133
|
+
:error-messages="$ta(inputErrors?.phone)"
|
|
134
|
+
></v-text-field>
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.newPassword') }}</div>
|
|
138
|
+
<!-- NEW PASSWORD-->
|
|
139
|
+
<v-text-field
|
|
140
|
+
:variant="variant"
|
|
141
|
+
id="new-password-input"
|
|
142
|
+
v-model="form.password"
|
|
143
|
+
:type="passwordVisibility ? 'text': 'password'"
|
|
144
|
+
required
|
|
145
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
146
|
+
:append-inner-icon="passwordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
147
|
+
@click:append-inner="passwordVisibility = !passwordVisibility"
|
|
148
|
+
autocomplete="new-password"
|
|
149
|
+
:error-messages="$ta(inputErrors?.newPassword)"
|
|
150
|
+
></v-text-field>
|
|
151
|
+
<div class="text-subtitle-1 text-medium-emphasis">{{ t('user.field.confirmPassword') }}</div>
|
|
152
|
+
<!-- CONFIRM PASSWORD-->
|
|
153
|
+
<v-text-field
|
|
154
|
+
:variant="variant"
|
|
155
|
+
id="confirm-password-input"
|
|
156
|
+
v-model="form.confirmPassword"
|
|
157
|
+
:type="confirmPasswordVisibility ? 'text': 'password'"
|
|
158
|
+
required
|
|
159
|
+
prepend-inner-icon="mdi-lock-outline"
|
|
160
|
+
:append-inner-icon="confirmPasswordVisibility ? 'mdi-eye-off': 'mdi-eye'"
|
|
161
|
+
@click:append-inner="confirmPasswordVisibility = !confirmPasswordVisibility"
|
|
162
|
+
autocomplete="new-password"
|
|
163
|
+
:error-messages="$ta(inputErrors?.confirmPassword)"
|
|
164
|
+
:rules="[confirmPasswordRule]"
|
|
165
|
+
></v-text-field>
|
|
166
|
+
</v-card-text>
|
|
167
|
+
<v-card-actions>
|
|
168
|
+
|
|
169
|
+
<v-btn
|
|
170
|
+
color="grey"
|
|
171
|
+
variant="text"
|
|
172
|
+
id="submit-button"
|
|
173
|
+
type="submit"
|
|
174
|
+
href="/login"
|
|
175
|
+
>
|
|
176
|
+
{{ t('auth.login') }}
|
|
177
|
+
</v-btn>
|
|
178
|
+
<v-spacer></v-spacer>
|
|
179
|
+
<v-btn
|
|
180
|
+
color="blue"
|
|
181
|
+
variant="tonal"
|
|
182
|
+
id="submit-button"
|
|
183
|
+
type="submit"
|
|
184
|
+
:loading="loading"
|
|
185
|
+
>
|
|
186
|
+
{{ t('action.sent') }}
|
|
187
|
+
</v-btn>
|
|
188
|
+
</v-card-actions>
|
|
189
|
+
</v-card>
|
|
190
|
+
</v-form>
|
|
191
|
+
</template>
|
|
192
|
+
</v-sheet>
|
|
193
|
+
</template>
|
|
194
|
+
|
|
195
|
+
<style scoped lang="sass">
|
|
196
|
+
// Your styles here
|
|
197
|
+
</style>
|
|
@@ -30,6 +30,18 @@ export function useAuth() {
|
|
|
30
30
|
return await authSystem.changeOwnPassword(currentPassword, newPassword)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const recoveryPasswordRequest = async (email: string) => {
|
|
34
|
+
return await authSystem.recoveryPasswordRequest(email)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const recoveryPasswordComplete = async (recoveryCode: string, newPassword: string) => {
|
|
38
|
+
return await authSystem.recoveryPasswordComplete(recoveryCode, newPassword)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const register = async (form: any) => {
|
|
42
|
+
return await authSystem.register(form)
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
const changeAvatar = async (file: File) => {
|
|
34
46
|
if (file) {
|
|
35
47
|
await authSystem.changeAvatar(file)
|
|
@@ -73,6 +85,13 @@ export function useAuth() {
|
|
|
73
85
|
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
login, logout, loginWithToken,
|
|
90
|
+
tokenIsValid, hasPermission,
|
|
91
|
+
isAuthenticated, fetchAuthUser,
|
|
92
|
+
changeOwnPassword, changeAvatar,
|
|
93
|
+
recoveryPasswordRequest, recoveryPasswordComplete,
|
|
94
|
+
register
|
|
95
|
+
}
|
|
77
96
|
|
|
78
97
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import RoleCrudPage from "./pages/crud/RoleCrudPage.vue";
|
|
|
13
13
|
|
|
14
14
|
import LoginPage from "./pages/LoginPage.vue";
|
|
15
15
|
import ProfilePage from "./pages/ProfilePage.vue";
|
|
16
|
-
import PasswordPage from "./pages/
|
|
16
|
+
import PasswordPage from "./pages/PasswordChangePage.vue";
|
|
17
17
|
|
|
18
18
|
import TenantView from "./views/TenantView.vue";
|
|
19
19
|
import TenantCrudPage from "./pages/crud/TenantCrudPage.vue";
|
|
@@ -39,6 +39,8 @@ import RoleCrud from "./cruds/role-crud/RoleCrud"
|
|
|
39
39
|
|
|
40
40
|
import {useAuthStore} from "./stores/auth/AuthStore.js";
|
|
41
41
|
|
|
42
|
+
import IdentityAuthRoutes from "./routes/IdentityAuthRoutes.js";
|
|
43
|
+
import IdentityCrudRoutes from "./routes/IdentityCrudRoutes.js";
|
|
42
44
|
import IdentityRoutes from "./routes/IdentityRoutes.js";
|
|
43
45
|
|
|
44
46
|
export {
|
|
@@ -90,5 +92,7 @@ export {
|
|
|
90
92
|
useAuthStore,
|
|
91
93
|
|
|
92
94
|
//Routes
|
|
93
|
-
|
|
95
|
+
IdentityCrudRoutes,
|
|
96
|
+
IdentityAuthRoutes,
|
|
97
|
+
IdentityRoutes,
|
|
94
98
|
}
|
package/src/pages/LoginPage.vue
CHANGED
|
@@ -31,7 +31,7 @@ function onLoginSuccess(){
|
|
|
31
31
|
<span class="pa-3 font-weight-medium rounded logo">{{TITLE_MAIN}}</span> {{TITLE_SEC}}
|
|
32
32
|
</h2>
|
|
33
33
|
|
|
34
|
-
<IdentityLogin @loginSuccess="onLoginSuccess"></IdentityLogin>
|
|
34
|
+
<IdentityLogin @loginSuccess="onLoginSuccess" recovery register></IdentityLogin>
|
|
35
35
|
</v-col>
|
|
36
36
|
</v-row>
|
|
37
37
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import IdentityRecoveryPasswordComplete from "../components/IdentityRecoveryPasswordComplete/IdentityRecoveryPasswordComplete.vue";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<v-container fluid class="fill-height">
|
|
8
|
+
|
|
9
|
+
<v-row justify="center" align="center">
|
|
10
|
+
|
|
11
|
+
<v-col cols="12" sm="8" md="6" lg="5">
|
|
12
|
+
<identity-recovery-password-complete></identity-recovery-password-complete>
|
|
13
|
+
</v-col>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
</v-row>
|
|
17
|
+
|
|
18
|
+
</v-container>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<style scoped>
|
|
22
|
+
|
|
23
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import IdentityRecoveryPasswordRequest
|
|
4
|
+
from "../components/IdentityRecoveryPasswordRequest/IdentityRecoveryPasswordRequest.vue";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<v-container fluid class="fill-height">
|
|
9
|
+
|
|
10
|
+
<v-row justify="center" align="center">
|
|
11
|
+
|
|
12
|
+
<v-col cols="12" sm="8" md="6" lg="5">
|
|
13
|
+
<identity-recovery-password-request></identity-recovery-password-request>
|
|
14
|
+
</v-col>
|
|
15
|
+
|
|
16
|
+
</v-row>
|
|
17
|
+
|
|
18
|
+
</v-container>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<style scoped>
|
|
22
|
+
|
|
23
|
+
</style>
|
|
@@ -15,7 +15,7 @@ const { t } = useI18n();
|
|
|
15
15
|
<v-card>
|
|
16
16
|
<identity-profile-view> </identity-profile-view>
|
|
17
17
|
<v-card-text class="text-center">
|
|
18
|
-
<v-btn color="blue" variant="tonal" @click="router.push({name: '
|
|
18
|
+
<v-btn color="blue" variant="tonal" @click="router.push({name: 'PasswordChange'})">
|
|
19
19
|
{{t('user.action.changeOwnPassword')}}
|
|
20
20
|
</v-btn>
|
|
21
21
|
</v-card-text>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import IdentityRegistration from "../components/IdentityRegistration/IdentityRegistration.vue";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<v-container fluid class="fill-height">
|
|
8
|
+
|
|
9
|
+
<v-row justify="center" align="center">
|
|
10
|
+
|
|
11
|
+
<v-col cols="12" sm="8" md="6" lg="5">
|
|
12
|
+
<identity-registration></identity-registration>
|
|
13
|
+
</v-col>
|
|
14
|
+
|
|
15
|
+
</v-row>
|
|
16
|
+
|
|
17
|
+
</v-container>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<style scoped>
|
|
21
|
+
|
|
22
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import LoginPage from '../pages/LoginPage.vue'
|
|
2
|
+
import ProfilePage from '../pages/ProfilePage.vue'
|
|
3
|
+
import PasswordPage from '../pages/PasswordChangePage.vue'
|
|
4
|
+
import RegistrationPage from '../pages/RegistrationPage.vue'
|
|
5
|
+
import PasswordRecoveryRequestPage from '../pages/PasswordRecoveryRequestPage.vue'
|
|
6
|
+
import PasswordRecoveryCompletePage from '../pages/PasswordRecoveryCompletePage.vue'
|
|
7
|
+
|
|
8
|
+
const routes = [
|
|
9
|
+
{
|
|
10
|
+
name: 'IdentityLogin',
|
|
11
|
+
path: '/login',
|
|
12
|
+
component: LoginPage,
|
|
13
|
+
meta: {
|
|
14
|
+
auth: false,
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Profile',
|
|
19
|
+
path: '/profile',
|
|
20
|
+
component: ProfilePage,
|
|
21
|
+
meta: {
|
|
22
|
+
auth: true,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'PasswordChange',
|
|
27
|
+
path: '/password/change',
|
|
28
|
+
component: PasswordPage,
|
|
29
|
+
meta: {
|
|
30
|
+
auth: true,
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Registration',
|
|
35
|
+
path: '/registration',
|
|
36
|
+
component: RegistrationPage,
|
|
37
|
+
meta: {
|
|
38
|
+
auth: false,
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'PasswordRecoveryRequest',
|
|
43
|
+
path: '/password/recovery/request',
|
|
44
|
+
component: PasswordRecoveryRequestPage,
|
|
45
|
+
meta: {
|
|
46
|
+
auth: false,
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'PasswordRecoveryComplete',
|
|
51
|
+
path: '/password/recovery/complete/:code',
|
|
52
|
+
component: PasswordRecoveryCompletePage,
|
|
53
|
+
meta: {
|
|
54
|
+
auth: false,
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export default routes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import UserCrudPage from '../pages/crud/UserCrudPage.vue'
|
|
2
|
+
import RoleCrudPage from '../pages/crud/RoleCrudPage.vue'
|
|
3
|
+
import TenantCrudPage from '../pages/crud/TenantCrudPage.vue'
|
|
4
|
+
import UserApiKeyCrudPage from '../pages/crud/UserApiKeyCrudPage.vue'
|
|
5
|
+
|
|
6
|
+
const routes = [
|
|
7
|
+
|
|
8
|
+
//CRUDS
|
|
9
|
+
{
|
|
10
|
+
name: 'CrudRole',
|
|
11
|
+
path: '/crud/role',
|
|
12
|
+
component: RoleCrudPage,
|
|
13
|
+
meta: {
|
|
14
|
+
auth: true,
|
|
15
|
+
permission: 'role:manage'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
name: 'CrudTenant',
|
|
21
|
+
path: '/crud/tenant',
|
|
22
|
+
component: TenantCrudPage,
|
|
23
|
+
meta: {
|
|
24
|
+
auth: true,
|
|
25
|
+
permission: 'tenant:manage'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
name: 'CrudUser',
|
|
31
|
+
path: '/crud/user',
|
|
32
|
+
component: UserCrudPage,
|
|
33
|
+
meta: {
|
|
34
|
+
auth: true,
|
|
35
|
+
permission: 'user:manage'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'CrudUserApiKey',
|
|
40
|
+
path: '/crud/user-api-key',
|
|
41
|
+
component: UserApiKeyCrudPage,
|
|
42
|
+
meta: {
|
|
43
|
+
auth: true,
|
|
44
|
+
permission: 'userApiKey:manage'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export default routes
|
|
@@ -1,78 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import PasswordPage from '../pages/PasswordPage.vue'
|
|
4
|
-
import UserCrudPage from '../pages/crud/UserCrudPage.vue'
|
|
5
|
-
import RoleCrudPage from '../pages/crud/RoleCrudPage.vue'
|
|
6
|
-
import TenantCrudPage from '../pages/crud/TenantCrudPage.vue'
|
|
7
|
-
import UserApiKeyCrudPage from '../pages/crud/UserApiKeyCrudPage.vue'
|
|
8
|
-
|
|
9
|
-
const routes = [
|
|
10
|
-
{
|
|
11
|
-
name: 'IdentityLogin',
|
|
12
|
-
path: '/identity-login',
|
|
13
|
-
component: LoginPage,
|
|
14
|
-
meta: {
|
|
15
|
-
auth: false,
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: 'Profile',
|
|
20
|
-
path: '/profile',
|
|
21
|
-
component: ProfilePage,
|
|
22
|
-
meta: {
|
|
23
|
-
auth: true,
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'Password',
|
|
28
|
-
path: '/password',
|
|
29
|
-
component: PasswordPage,
|
|
30
|
-
meta: {
|
|
31
|
-
auth: true,
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
//CRUDS
|
|
35
|
-
{
|
|
36
|
-
name: 'CrudRole',
|
|
37
|
-
path: '/crud/role',
|
|
38
|
-
component: RoleCrudPage,
|
|
39
|
-
meta: {
|
|
40
|
-
auth: true,
|
|
41
|
-
permission: 'role:manage'
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
name: 'CrudTenant',
|
|
47
|
-
path: '/crud/tenant',
|
|
48
|
-
component: TenantCrudPage,
|
|
49
|
-
meta: {
|
|
50
|
-
auth: true,
|
|
51
|
-
permission: 'tenant:manage'
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
name: 'CrudUser',
|
|
57
|
-
path: '/crud/user',
|
|
58
|
-
component: UserCrudPage,
|
|
59
|
-
meta: {
|
|
60
|
-
auth: true,
|
|
61
|
-
permission: 'user:manage'
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'CrudUserApiKey',
|
|
66
|
-
path: '/crud/user-api-key',
|
|
67
|
-
component: UserApiKeyCrudPage,
|
|
68
|
-
meta: {
|
|
69
|
-
auth: true,
|
|
70
|
-
permission: 'userApiKey:manage'
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
|
|
1
|
+
import IdentityCrudRoutes from './IdentityCrudRoutes'
|
|
2
|
+
import IdentityAuthRoutes from './IdentityAuthRoutes'
|
|
74
3
|
|
|
75
4
|
|
|
5
|
+
const routes = [
|
|
6
|
+
...IdentityAuthRoutes,
|
|
7
|
+
...IdentityCrudRoutes,
|
|
76
8
|
]
|
|
77
9
|
|
|
78
10
|
|
package/src/i18n/I18n.ts
DELETED
package/src/i18n/I18nMessages.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import {CommonI18nMessages} from "@drax/common-front"
|
|
2
|
-
import {IdentityI18nMessages} from "@drax/identity-front"
|
|
3
|
-
import merge from 'deepmerge'
|
|
4
|
-
|
|
5
|
-
const mainMsg = {
|
|
6
|
-
en: {
|
|
7
|
-
main: {
|
|
8
|
-
home: 'Home',
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
es: {
|
|
12
|
-
main: {
|
|
13
|
-
home: 'Principal',
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const messages = merge.all([mainMsg,IdentityI18nMessages, CommonI18nMessages])
|
|
19
|
-
|
|
20
|
-
console.log("messages",messages)
|
|
21
|
-
|
|
22
|
-
export default messages
|
|
File without changes
|