Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
dtd
/
civitas.ui
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit fb62f636
authored
Apr 24, 2025
by
Samuel Taniel Mulyadi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add token expired
1 parent
d7205995
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
254 additions
and
107 deletions
@core/scss/template/pages/page-auth.scss
@core/stores/keycloakStore.ts
layouts/components/DefaultLayoutWithHorizontalNav.vue
layouts/components/DefaultLayoutWithVerticalNav.vue
layouts/components/NavbarTokenExpiredTime.vue
plugins/keycloak.ts
views/dstipro/beranda/keamanan/index.vue
@core/scss/template/pages/page-auth.scss
View file @
fb62f63
...
@@ -75,7 +75,7 @@
...
@@ -75,7 +75,7 @@
min-block-size
:
100vh
;
/* Ensures the element covers the full viewport height */
min-block-size
:
100vh
;
/* Ensures the element covers the full viewport height */
padding-block-start
:
10px
;
padding-block-start
:
10px
;
// margin: 5% 5%;marginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmargin
// margin: 5% 5%;marginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmargin
margin
}
}
.main-component-margin
{
.main-component-margin
{
...
...
@core/stores/keycloakStore.ts
View file @
fb62f63
...
@@ -13,6 +13,7 @@ const username = ref<string>('')
...
@@ -13,6 +13,7 @@ const username = ref<string>('')
const
civitas
=
ref
<
string
>
(
''
)
const
civitas
=
ref
<
string
>
(
''
)
const
kodeIdentitas
=
ref
<
string
>
(
''
)
const
kodeIdentitas
=
ref
<
string
>
(
''
)
const
accessToken
=
ref
<
string
>
(
''
)
const
accessToken
=
ref
<
string
>
(
''
)
const
tokenParsedExp
=
ref
<
number
>
(
0
)
const
refreshTokenExp
=
ref
<
number
>
(
0
)
const
refreshTokenExp
=
ref
<
number
>
(
0
)
const
roles
=
ref
<
string
[]
>
([])
const
roles
=
ref
<
string
[]
>
([])
const
selectedRole
=
useStorage
(
'selectedRole'
,
'admin'
)
const
selectedRole
=
useStorage
(
'selectedRole'
,
'admin'
)
...
@@ -28,6 +29,7 @@ const refresh = (): void => {
...
@@ -28,6 +29,7 @@ const refresh = (): void => {
username
.
value
=
tokenParsed
.
preferred_username
||
''
username
.
value
=
tokenParsed
.
preferred_username
||
''
civitas
.
value
=
tokenParsed
.
civitas
||
''
civitas
.
value
=
tokenParsed
.
civitas
||
''
kodeIdentitas
.
value
=
tokenParsed
.
kodeIdentitas
||
''
kodeIdentitas
.
value
=
tokenParsed
.
kodeIdentitas
||
''
tokenParsedExp
.
value
=
tokenParsed
.
exp
||
0
accessToken
.
value
=
keycloakInstance
.
token
||
''
accessToken
.
value
=
keycloakInstance
.
token
||
''
refreshTokenExp
.
value
=
refreshedTokenParsed
.
exp
||
0
refreshTokenExp
.
value
=
refreshedTokenParsed
.
exp
||
0
roles
.
value
=
keycloakInstance
.
resourceAccess
?.
vueplayground
?.
roles
??
[]
roles
.
value
=
keycloakInstance
.
resourceAccess
?.
vueplayground
?.
roles
??
[]
...
...
layouts/components/DefaultLayoutWithHorizontalNav.vue
View file @
fb62f63
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
HorizontalNavLayout
}
from
'@layouts'
import
{
HorizontalNavLayout
}
from
'@layouts'
import
navItems
from
'@/navigation/horizontal'
import
{
computed
,
onMounted
,
onUnmounted
,
ref
}
from
'vue'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
keycloakInstance
from
'@/keycloak'
// Components
import
Footer
from
'@/layouts/components/Footer.vue'
import
Footer
from
'@/layouts/components/Footer.vue'
import
NavbarThemeSwitcher
from
'@/layouts/components/NavbarThemeSwitcher.vue'
import
NavbarThemeSwitcher
from
'@/layouts/components/NavbarThemeSwitcher.vue'
import
NavbarTokenExpiredTime
from
'@/layouts/components/NavbarTokenExpiredTime.vue'
import
UserProfile
from
'@/layouts/components/UserProfile.vue'
import
UserProfile
from
'@/layouts/components/UserProfile.vue'
import
navItems
from
'@/navigation/horizontal'
// Keycloak & state
const
keycloakStore
=
useKeycloakStore
()
const
authenticated
=
computed
(()
=>
keycloakStore
.
authenticated
)
const
now
=
ref
(
Math
.
floor
(
Date
.
now
()
/
1000
))
const
tokenLifetime
=
ref
(
0
)
let
timer
:
ReturnType
<
typeof
setInterval
>
onMounted
(()
=>
{
// Set tokenLifetime only once on mount
if
(
keycloakInstance
.
tokenParsed
?.
exp
&&
keycloakInstance
.
tokenParsed
?.
iat
)
tokenLifetime
.
value
=
keycloakInstance
.
tokenParsed
.
exp
-
keycloakInstance
.
tokenParsed
.
iat
// Start timer
timer
=
setInterval
(()
=>
{
now
.
value
=
Math
.
floor
(
Date
.
now
()
/
1000
)
},
1000
)
})
onUnmounted
(()
=>
{
clearInterval
(
timer
)
})
// Computed expiration values
const
computedExpIn
=
computed
(()
=>
{
return
authenticated
.
value
&&
keycloakInstance
.
tokenParsed
?.
exp
?
Math
.
max
(
keycloakInstance
.
tokenParsed
?.
exp
-
now
.
value
,
0
)
:
0
})
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -39,10 +73,27 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
...
@@ -39,10 +73,27 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
:languages=
"themeConfig.app.i18n.langConfig"
:languages=
"themeConfig.app.i18n.langConfig"
/>
/>
-->
-->
<NavbarTokenExpiredTime
/>
<NavbarThemeSwitcher
/>
<NavbarThemeSwitcher
/>
<!--
<NavbarShortcuts
/>
-->
<!--
<NavbarShortcuts
/>
-->
<!--
<NavBarNotifications
class=
"me-2"
/>
-->
<!--
<NavBarNotifications
class=
"me-2"
/>
-->
<!--
<div
class=
"pa-4"
>
<VAlert
v-if=
"authenticated"
type=
"info"
border=
"start"
variant=
"outlined"
class=
"mb-4"
>
<h3
class=
"text-h6 font-weight-bold"
>
Access token expires in
{{
computedExpIn
}}
sec,
Refresh token in
{{
computedRefExpIn
}}
sec,
Session:
{{
computedSessionTimer
}}
</h3>
</VAlert>
</div>
-->
<UserProfile
/>
<UserProfile
/>
</
template
>
</
template
>
...
...
layouts/components/DefaultLayoutWithVerticalNav.vue
View file @
fb62f63
...
@@ -5,6 +5,7 @@ import navItems from '@/navigation/vertical'
...
@@ -5,6 +5,7 @@ import navItems from '@/navigation/vertical'
// Components
// Components
import
Footer
from
'@/layouts/components/Footer.vue'
import
Footer
from
'@/layouts/components/Footer.vue'
import
NavbarThemeSwitcher
from
'@/layouts/components/NavbarThemeSwitcher.vue'
import
NavbarThemeSwitcher
from
'@/layouts/components/NavbarThemeSwitcher.vue'
import
NavbarTokenExpiredTime
from
'@/layouts/components/NavbarTokenExpiredTime.vue'
import
UserProfile
from
'@/layouts/components/UserProfile.vue'
import
UserProfile
from
'@/layouts/components/UserProfile.vue'
// @layouts plugin
// @layouts plugin
...
@@ -41,6 +42,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
...
@@ -41,6 +42,7 @@ import UserProfile from '@/layouts/components/UserProfile.vue'
:languages=
"themeConfig.app.i18n.langConfig"
:languages=
"themeConfig.app.i18n.langConfig"
/>
/>
-->
-->
<NavbarTokenExpiredTime
/>
<NavbarThemeSwitcher
/>
<NavbarThemeSwitcher
/>
<!--
<NavbarShortcuts
/>
-->
<!--
<NavbarShortcuts
/>
-->
<!--
<NavBarNotifications
class=
"me-2"
/>
-->
<!--
<NavBarNotifications
class=
"me-2"
/>
-->
...
...
layouts/components/NavbarTokenExpiredTime.vue
0 → 100644
View file @
fb62f63
<
script
setup
lang=
"ts"
>
import
{
computed
,
onMounted
,
onUnmounted
,
ref
}
from
'vue'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
keycloakInstance
from
'@/keycloak'
const
keycloakStore
=
useKeycloakStore
()
const
authenticated
=
computed
(()
=>
keycloakStore
.
authenticated
)
const
now
=
ref
(
Math
.
floor
(
Date
.
now
()
/
1000
))
const
tokenLifetime
=
ref
(
0
)
let
timer
:
ReturnType
<
typeof
setInterval
>
onMounted
(()
=>
{
// Set tokenLifetime only once
if
(
keycloakInstance
.
tokenParsed
?.
exp
&&
keycloakInstance
.
tokenParsed
?.
iat
)
tokenLifetime
.
value
=
keycloakInstance
.
tokenParsed
.
exp
-
keycloakInstance
.
tokenParsed
.
iat
timer
=
setInterval
(()
=>
{
now
.
value
=
Math
.
floor
(
Date
.
now
()
/
1000
)
},
1000
)
})
onUnmounted
(()
=>
{
clearInterval
(
timer
)
})
const
computedExpIn
=
computed
(()
=>
{
return
authenticated
.
value
&&
keycloakInstance
.
tokenParsed
?.
exp
?
Math
.
max
(
keycloakInstance
.
tokenParsed
.
exp
-
now
.
value
,
0
)
:
0
})
</
script
>
<
template
>
<div
v-if=
"authenticated"
class=
"me-4"
style=
"position: relative; block-size: 40px; inline-size: 40px;"
>
<VProgressCircular
:model-value=
"(computedExpIn / tokenLifetime) * 100"
:size=
"40"
:width=
"3"
color=
"primary"
/>
<div
class=
"text-subtitle-1 font-weight-bold"
style=
"position: absolute; inset-block-start: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%);"
>
{{
computedExpIn
}}
</div>
</div>
</
template
>
plugins/keycloak.ts
View file @
fb62f63
...
@@ -15,6 +15,19 @@ export default defineNuxtPlugin(async nuxtApp => {
...
@@ -15,6 +15,19 @@ export default defineNuxtPlugin(async nuxtApp => {
keycloakStore
.
refresh
()
keycloakStore
.
refresh
()
console
.
log
(
'User is authenticated'
)
console
.
log
(
'User is authenticated'
)
navigateTo
(
'/profile'
)
navigateTo
(
'/profile'
)
setInterval
(()
=>
{
const
now
=
Math
.
floor
(
Date
.
now
()
/
1000
)
const
tokenExp
=
keycloakInstance
.
tokenParsed
?.
exp
??
0
if
(
tokenExp
<=
now
)
{
console
.
warn
(
'Token expired. Logging out...'
)
keycloakInstance
.
logout
({
redirectUri
:
`
${
window
.
location
.
origin
}
/login`
})
}
else
{
console
.
log
(
'Token expires in:'
,
tokenExp
-
now
,
'seconds'
)
}
},
10
_000
)
}
}
else
{
else
{
console
.
log
(
'User is not authenticated'
)
console
.
log
(
'User is not authenticated'
)
...
...
views/dstipro/beranda/keamanan/index.vue
View file @
fb62f63
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
useKeycloakStore
}
from
"@core/stores/keycloakStore"
;
import
{
useKeycloakStore
}
from
'@core/stores/keycloakStore'
const
keycloakStore
=
useKeycloakStore
()
;
const
keycloakStore
=
useKeycloakStore
()
const
isCurrentPasswordVisible
=
ref
(
false
)
;
const
isCurrentPasswordVisible
=
ref
(
false
)
const
isNewPasswordVisible
=
ref
(
false
)
;
const
isNewPasswordVisible
=
ref
(
false
)
const
isConfirmPasswordVisible
=
ref
(
false
)
;
const
isConfirmPasswordVisible
=
ref
(
false
)
const
currentPassword
=
ref
(
""
);
const
currentPassword
=
ref
(
''
)
const
newPassword
=
ref
(
""
);
const
newPassword
=
ref
(
''
)
const
newPasswordError
=
ref
(
""
);
const
newPasswordError
=
ref
(
''
)
const
confirmPassword
=
ref
(
""
);
const
confirmPassword
=
ref
(
''
)
const
isSubmitting
=
ref
(
false
)
;
const
isSubmitting
=
ref
(
false
)
watch
(
newPassword
,
(
newValue
)
=>
{
watch
(
newPassword
,
newValue
=>
{
if
(
currentPassword
.
value
&&
newValue
===
currentPassword
.
value
)
{
if
(
currentPassword
.
value
&&
newValue
===
currentPassword
.
value
)
{
newPasswordError
.
value
=
newPasswordError
.
value
"Kata sandi baru tidak boleh sama dengan kata sandi lama."
;
=
'Kata sandi baru tidak boleh sama dengan kata sandi lama.'
}
else
{
newPasswordError
.
value
=
""
;
}
}
});
else
{
newPasswordError
.
value
=
''
}
})
const
passwordRequirements
=
[
const
passwordRequirements
=
[
"Panjang minimal 8 karakter, maksimal 20 karakter"
,
'Panjang minimal 8 karakter, maksimal 20 karakter'
,
"Minimal satu karakter huruf besar"
,
'Minimal satu karakter huruf besar'
,
"Minimal satu angka"
,
'Minimal satu angka'
,
"Minimal satu simbol, atau karakter spasi"
,
'Minimal satu simbol, atau karakter spasi'
,
]
;
]
// Aturan Validasi
// Aturan Validasi
const
oldPasswordRules
=
[
const
oldPasswordRules
=
[
(
v
:
string
)
=>
!!
v
||
"Konfirmasi kata sandi diperlukan"
,
(
v
:
string
)
=>
!!
v
||
'Konfirmasi kata sandi diperlukan'
,
]
;
]
const
passwordRules
=
[
const
passwordRules
=
[
(
v
:
string
)
=>
!!
v
||
"Kata sandi diperlukan"
,
(
v
:
string
)
=>
!!
v
||
'Kata sandi diperlukan'
,
(
v
:
string
)
=>
v
.
length
>=
8
||
"Kata sandi minimal terdiri dari 8 karakter"
,
(
v
:
string
)
=>
v
.
length
>=
8
||
'Kata sandi minimal terdiri dari 8 karakter'
,
(
v
:
string
)
=>
(
v
:
string
)
=>
/
[
a-z
]
/
.
test
(
v
)
||
"Kata sandi setidaknya mengandung satu huruf kecil"
,
/
[
a-z
]
/
.
test
(
v
)
||
'Kata sandi setidaknya mengandung satu huruf kecil'
,
(
v
:
string
)
=>
(
v
:
string
)
=>
/
[
A-Z
]
/
.
test
(
v
)
||
"Kata sandi setidaknya mengandung satu huruf besar"
,
/
[
A-Z
]
/
.
test
(
v
)
||
'Kata sandi setidaknya mengandung satu huruf besar'
,
(
v
:
string
)
=>
/
\d
/
.
test
(
v
)
||
"Kata sandi setidaknya berisi satu angka"
,
(
v
:
string
)
=>
/
\d
/
.
test
(
v
)
||
'Kata sandi setidaknya berisi satu angka'
,
(
v
:
string
)
=>
(
v
:
string
)
=>
/
[
!"#$%&'()*+,-.
/
:;<=>?@[
\\\]
^_`{|}~
]
/
.
test
(
v
)
||
/
[
!"#$%&'()*+,-.
/
:;<=>?@[
\\\]
^_`{|}~
]
/
.
test
(
v
)
"Kata sandi setidaknya mengandung satu simbol atau spasi"
,
||
'Kata sandi setidaknya mengandung satu simbol atau spasi'
,
]
;
]
const
confirmPasswordRules
=
[
const
confirmPasswordRules
=
[
(
v
:
string
)
=>
!!
v
||
"Konfirmasi kata sandi diperlukan"
,
(
v
:
string
)
=>
!!
v
||
'Konfirmasi kata sandi diperlukan'
,
(
v
:
string
)
=>
v
===
newPassword
.
value
||
"Kata sandi tidak cocok"
,
(
v
:
string
)
=>
v
===
newPassword
.
value
||
'Kata sandi tidak cocok'
,
]
;
]
// Generate Password
// Generate Password
function
generatePassword
(
length
:
number
=
10
):
string
{
function
generatePassword
(
length
:
number
=
10
):
string
{
const
lowercase
=
"abcdefghijklmnopqrstuvwxyz"
;
const
lowercase
=
'abcdefghijklmnopqrstuvwxyz'
const
uppercase
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
;
const
uppercase
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const
numbers
=
"0123456789"
;
const
numbers
=
'0123456789'
const
symbols
=
` !"#$%&'()*+,-./:;<=>?@[\\]^_\`{|}~`
;
const
symbols
=
' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
// At least one character from each required category
// At least one character from each required category
const
mandatoryCharacters
=
[
const
mandatoryCharacters
=
[
...
@@ -64,113 +65,119 @@ function generatePassword(length: number = 10): string {
...
@@ -64,113 +65,119 @@ function generatePassword(length: number = 10): string {
uppercase
[
Math
.
floor
(
Math
.
random
()
*
uppercase
.
length
)],
uppercase
[
Math
.
floor
(
Math
.
random
()
*
uppercase
.
length
)],
numbers
[
Math
.
floor
(
Math
.
random
()
*
numbers
.
length
)],
numbers
[
Math
.
floor
(
Math
.
random
()
*
numbers
.
length
)],
symbols
[
Math
.
floor
(
Math
.
random
()
*
symbols
.
length
)],
symbols
[
Math
.
floor
(
Math
.
random
()
*
symbols
.
length
)],
]
;
]
// Combine all character sets into one charset
// Combine all character sets into one charset
const
allCharacters
=
lowercase
+
uppercase
+
numbers
+
symbols
;
const
allCharacters
=
lowercase
+
uppercase
+
numbers
+
symbols
const
remainingLength
=
length
-
mandatoryCharacters
.
length
;
const
remainingLength
=
length
-
mandatoryCharacters
.
length
let
password
=
mandatoryCharacters
;
let
password
=
mandatoryCharacters
// Fill the rest of the password with random characters from the combined charset
// Fill the rest of the password with random characters from the combined charset
for
(
let
i
=
0
;
i
<
remainingLength
;
i
++
)
{
for
(
let
i
=
0
;
i
<
remainingLength
;
i
++
)
{
const
randomChar
=
const
randomChar
allCharacters
[
Math
.
floor
(
Math
.
random
()
*
allCharacters
.
length
)];
=
allCharacters
[
Math
.
floor
(
Math
.
random
()
*
allCharacters
.
length
)]
password
.
push
(
randomChar
);
password
.
push
(
randomChar
)
}
}
// Shuffle the password to ensure randomness
// Shuffle the password to ensure randomness
password
=
password
.
sort
(()
=>
Math
.
random
()
-
0.5
)
;
password
=
password
.
sort
(()
=>
Math
.
random
()
-
0.5
)
// Return the password as a string
// Return the password as a string
return
password
.
join
(
""
);
return
password
.
join
(
''
)
}
}
// fungsi untuk encoding Base64
// fungsi untuk encoding Base64
function
toBase64
(
str
:
string
)
{
function
toBase64
(
str
:
string
)
{
return
btoa
(
unescape
(
encodeURIComponent
(
str
)))
;
return
btoa
(
unescape
(
encodeURIComponent
(
str
)))
}
}
// Set Password
// Set Password
async
function
setPassword
()
{
async
function
setPassword
()
{
isSubmitting
.
value
=
true
;
isSubmitting
.
value
=
true
if
(
isEmpty
(
currentPassword
.
value
))
{
if
(
isEmpty
(
currentPassword
.
value
))
{
alert
(
"Kata Sandi Lama tidak boleh kosong"
);
alert
(
'Kata Sandi Lama tidak boleh kosong'
)
return
;
return
}
}
// Buat objek data dengan password yang sudah di-encoding
// Buat objek data dengan password yang sudah di-encoding
const
requestData
=
{
const
requestData
=
{
oldPassword
:
toBase64
(
currentPassword
.
value
),
oldPassword
:
toBase64
(
currentPassword
.
value
),
newPassword
:
toBase64
(
newPassword
.
value
),
newPassword
:
toBase64
(
newPassword
.
value
),
}
;
}
if
(
if
(
newPassword
.
value
.
length
<
8
||
newPassword
.
value
.
length
<
8
!
/
[
a-z
]
/
.
test
(
newPassword
.
value
)
||
||
!
/
[
a-z
]
/
.
test
(
newPassword
.
value
)
!
/
\d
/
.
test
(
newPassword
.
value
)
||
||
!
/
\d
/
.
test
(
newPassword
.
value
)
!
/
[
!"#$%&'()*+,-.
/
:;<=>?@[
\]
^_`{|}~
]
/
.
test
(
newPassword
.
value
)
||
!
/
[
!"#$%&'()*+,-.
/
:;<=>?@[
\]
^_`{|}~
]
/
.
test
(
newPassword
.
value
)
)
{
)
{
alert
(
alert
(
"Kata Sandi harus memiliki minimal 8 karakter, setidaknya satu huruf besar, satu angka, dan satu karakter khusus."
'Kata Sandi harus memiliki minimal 8 karakter, setidaknya satu huruf besar, satu angka, dan satu karakter khusus.'
,
);
)
return
;
return
}
}
if
(
newPassword
.
value
===
currentPassword
.
value
)
{
if
(
newPassword
.
value
===
currentPassword
.
value
)
{
alert
(
"Kata sandi baru tidak boleh sama dengan kata sandi lama."
);
alert
(
'Kata sandi baru tidak boleh sama dengan kata sandi lama.'
)
return
;
return
}
}
if
(
newPassword
.
value
!==
confirmPassword
.
value
)
{
if
(
newPassword
.
value
!==
confirmPassword
.
value
)
{
alert
(
"Kata Sandi Baru dan Konfirmasi Kata Sandi tidak cocok."
);
alert
(
'Kata Sandi Baru dan Konfirmasi Kata Sandi tidak cocok.'
)
return
;
return
}
}
try
{
try
{
// Panggil API untuk mengganti password
// Panggil API untuk mengganti password
const
apiEndpoint
=
`https://api.ui.ac.id/my/pw`
;
const
apiEndpoint
=
'https://api.ui.ac.id/my/pw'
const
response
=
await
fetch
(
apiEndpoint
,
{
const
response
=
await
fetch
(
apiEndpoint
,
{
method
:
"POST"
,
method
:
'POST'
,
headers
:
{
headers
:
{
"Content-Type"
:
"application/json"
,
'Content-Type'
:
'application/json'
,
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
'Authorization'
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
},
},
body
:
JSON
.
stringify
(
requestData
),
body
:
JSON
.
stringify
(
requestData
),
})
;
})
// Check for HTTP status code 401 which indicates old password is incorrect
// Check for HTTP status code 401 which indicates old password is incorrect
if
(
response
.
status
===
401
)
{
if
(
response
.
status
===
401
)
throw
new
Error
(
"Kata sandi lama tidak sesuai"
);
throw
new
Error
(
'Kata sandi lama tidak sesuai'
)
}
if
(
!
response
.
ok
)
{
if
(
!
response
.
ok
)
{
const
errorData
=
await
response
.
json
()
;
const
errorData
=
await
response
.
json
()
throw
new
Error
(
errorData
.
message
||
"Gagal mengubah password"
);
throw
new
Error
(
errorData
.
message
||
'Gagal mengubah password'
)
}
}
// Jika berhasil, tampilkan pesan dan reset form
// Jika berhasil, tampilkan pesan dan reset form
alert
(
alert
(
"Kata sandi berhasil diubah. Silakan logout dan login kembali dengan kata sandi baru."
'Kata sandi berhasil diubah. Silakan logout dan login kembali dengan kata sandi baru.'
,
);
)
currentPassword
.
value
=
""
;
currentPassword
.
value
=
''
newPassword
.
value
=
""
;
newPassword
.
value
=
''
confirmPassword
.
value
=
""
;
confirmPassword
.
value
=
''
}
catch
(
error
)
{
}
catch
(
error
)
{
// Handle error
// Handle error
if
(
error
instanceof
Error
)
{
if
(
error
instanceof
Error
)
alert
(
error
.
message
)
;
alert
(
error
.
message
)
}
else
{
else
alert
(
"Terjadi kesalahan saat mengubah kata sandi"
);
alert
(
'Terjadi kesalahan saat mengubah kata sandi'
)
}
}
}
finally
{
finally
{
isSubmitting
.
value
=
false
;
isSubmitting
.
value
=
false
}
}
// Reset input
// Reset input
currentPassword
.
value
=
""
;
currentPassword
.
value
=
''
newPassword
.
value
=
""
;
newPassword
.
value
=
''
confirmPassword
.
value
=
""
;
confirmPassword
.
value
=
''
}
}
</
script
>
</
script
>
...
@@ -186,7 +193,10 @@ async function setPassword() {
...
@@ -186,7 +193,10 @@ async function setPassword() {
<VCardText
class=
"pt-0"
>
<VCardText
class=
"pt-0"
>
<!-- 👉 Current Password -->
<!-- 👉 Current Password -->
<VRow>
<VRow>
<VCol
cols=
"12"
md=
"6"
>
<VCol
cols=
"12"
md=
"6"
>
<!-- 👉 current password -->
<!-- 👉 current password -->
<VTextField
<VTextField
v-model=
"currentPassword"
v-model=
"currentPassword"
...
@@ -197,18 +207,21 @@ async function setPassword() {
...
@@ -197,18 +207,21 @@ async function setPassword() {
"
"
autocomplete=
"on"
autocomplete=
"on"
label=
"Kata Sandi Lama"
label=
"Kata Sandi Lama"
:rules=
"oldPasswordRules"
clearable
@
click:append-inner=
"
@
click:append-inner=
"
isCurrentPasswordVisible = !isCurrentPasswordVisible
isCurrentPasswordVisible = !isCurrentPasswordVisible
"
"
:rules=
"oldPasswordRules"
clearable
/>
/>
</VCol>
</VCol>
</VRow>
</VRow>
<!-- 👉 New Password -->
<!-- 👉 New Password -->
<VRow>
<VRow>
<VCol
cols=
"12"
md=
"6"
>
<VCol
cols=
"12"
md=
"6"
>
<!-- 👉 new password -->
<!-- 👉 new password -->
<VTextField
<VTextField
v-model=
"newPassword"
v-model=
"newPassword"
...
@@ -219,16 +232,19 @@ async function setPassword() {
...
@@ -219,16 +232,19 @@ async function setPassword() {
"
"
label=
"Kata Sandi Baru"
label=
"Kata Sandi Baru"
autocomplete=
"on"
autocomplete=
"on"
@
click:append-inner=
"
isNewPasswordVisible = !isNewPasswordVisible
"
:rules=
"passwordRules"
:rules=
"passwordRules"
:error-messages=
"newPasswordError"
:error-messages=
"newPasswordError"
clearable
clearable
@
click:append-inner=
"
isNewPasswordVisible = !isNewPasswordVisible
"
/>
/>
</VCol>
</VCol>
<VCol
cols=
"12"
md=
"6"
>
<VCol
cols=
"12"
md=
"6"
>
<!-- 👉 confirm password -->
<!-- 👉 confirm password -->
<VTextField
<VTextField
v-model=
"confirmPassword"
v-model=
"confirmPassword"
...
@@ -239,11 +255,11 @@ async function setPassword() {
...
@@ -239,11 +255,11 @@ async function setPassword() {
"
"
autocomplete=
"on"
autocomplete=
"on"
label=
"Konfirmasi Kata Sandi"
label=
"Konfirmasi Kata Sandi"
:rules=
"confirmPasswordRules"
clearable
@
click:append-inner=
"
@
click:append-inner=
"
isConfirmPasswordVisible = !isConfirmPasswordVisible
isConfirmPasswordVisible = !isConfirmPasswordVisible
"
"
:rules=
"confirmPasswordRules"
clearable
/>
/>
</VCol>
</VCol>
</VRow>
</VRow>
...
@@ -276,11 +292,20 @@ async function setPassword() {
...
@@ -276,11 +292,20 @@ async function setPassword() {
<!-- 👉 Action Buttons -->
<!-- 👉 Action Buttons -->
<div
class=
"d-flex flex-wrap gap-4"
>
<div
class=
"d-flex flex-wrap gap-4"
>
<VBtn
@
click=
"setPassword"
:disabled=
"isSubmitting"
>
{{
<VBtn
:disabled=
"isSubmitting"
@
click=
"setPassword"
>
{{
isSubmitting ? "Sedang Memproses..." : "Simpan Perubahan"
isSubmitting ? "Sedang Memproses..." : "Simpan Perubahan"
}}
</VBtn>
}}
</VBtn>
<VBtn
type=
"reset"
color=
"secondary"
variant=
"outlined"
>
<VBtn
type=
"reset"
color=
"secondary"
variant=
"outlined"
>
Reset
Reset
</VBtn>
</VBtn>
<VBtn
<VBtn
...
@@ -291,8 +316,8 @@ async function setPassword() {
...
@@ -291,8 +316,8 @@ async function setPassword() {
confirmPassword = newPassword;
confirmPassword = newPassword;
"
"
>
>
Generate Kata Sandi
</VBtn
Generate Kata Sandi
>
</VBtn
>
</div>
</div>
</VCardText>
</VCardText>
</VForm>
</VForm>
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment