Skip to content

Commit

Permalink
Add force password change on first login with temp password
Browse files Browse the repository at this point in the history
  • Loading branch information
NHAS committed Nov 23, 2024
1 parent 282449f commit 0446dbc
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 35 deletions.
14 changes: 10 additions & 4 deletions adminui/ui_webserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func New(firewall *router.Firewall, errs chan<- error) (ui *AdminUI, err error)
adminUI.startUpdateChecker(notifications)
}

protectedRoutes.HandleFunc("POST /api/change_password", adminUI.changePassword)
protectedRoutes.HandleFunc("PUT /api/change_password", adminUI.changePassword)

protectedRoutes.HandleFunc("GET /api/logout", func(w http.ResponseWriter, r *http.Request) {
adminUI.sessionManager.DeleteSession(w, r)
Expand Down Expand Up @@ -469,7 +469,7 @@ func (au *AdminUI) Close() {

func (au *AdminUI) changePassword(w http.ResponseWriter, r *http.Request) {

_, u := au.sessionManager.GetSessionFromRequest(r)
sessKey, u := au.sessionManager.GetSessionFromRequest(r)
if u == nil {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
return
Expand All @@ -486,7 +486,7 @@ func (au *AdminUI) changePassword(w http.ResponseWriter, r *http.Request) {
defer func() { au.respond(err, w) }()

var req ChangePasswordRequestDTO
err = json.NewDecoder(r.Body).Decode(&r)
err = json.NewDecoder(r.Body).Decode(&req)
r.Body.Close()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
Expand All @@ -495,7 +495,7 @@ func (au *AdminUI) changePassword(w http.ResponseWriter, r *http.Request) {

err = data.CompareAdminKeys(u.Username, req.CurrentPassword)
if err != nil {
log.Println("bad password for admin")
log.Println("bad password for admin (password change)")
err = errors.New("current password is incorrect")
w.WriteHeader(http.StatusUnauthorized)
return
Expand All @@ -510,4 +510,10 @@ func (au *AdminUI) changePassword(w http.ResponseWriter, r *http.Request) {
return
}

u.Change = false

au.sessionManager.UpdateSession(sessKey, *u)

log.Printf("admin %q changed their password", u.Username)

}
13 changes: 7 additions & 6 deletions adminui2/src/pages/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from "vue"
import { useToast } from 'vue-toastification'
import EmptyTable from '@/components/EmptyTable.vue'
Expand All @@ -8,6 +9,7 @@ import { useDevicesStore } from '@/stores/devices'
import { useTokensStore } from '@/stores/registration_tokens'
import { useAuthStore } from '@/stores/auth'
import { useInstanceDetailsStore } from '@/stores/serverInfo'
import { usePagination } from "@/composables/usePagination"
const devicesStore = useDevicesStore()
devicesStore.load(false)
Expand All @@ -18,11 +20,10 @@ registrationTokensStore.load(false)
const instanceDetails = useInstanceDetailsStore()
instanceDetails.load(true)
const usersStore = useUsersStore()
usersStore.load(false)
const authStore = useAuthStore()
const toast = useToast()
</script>

<template>
Expand All @@ -31,20 +32,20 @@ const toast = useToast()
<div class="mt-6 flex flex-wrap gap-6">
<div class="flex w-full gap-4">
<div class="flex grid w-1/2 grid-cols-2 gap-4 min-w-[405px]">
<router-link to="/users" class="card-compact bg-base-100 shadow-xl">
<router-link to="/management/users" class="card-compact bg-base-100 shadow-xl">
<div class="card-body">
<h5 class="card-title">Manage MFA</h5>

<div>{{ usersStore.users?.length == 0 ? 'No users' : usersStore.users?.length + ' users' }}</div>
</div>
</router-link>
<router-link to="/devices" class="card-compact bg-base-100 shadow-xl">
<router-link to="/management/devices" class="card-compact bg-base-100 shadow-xl">
<div class="card-body">
<h5 class="card-title">Manage Devices</h5>
<div>{{ devicesStore.numDevices() == 0 ? 'No devices' : devicesStore.numDevices() }}</div>
</div>
</router-link>
<router-link to="/devices" class="card-compact bg-base-100 shadow-xl">
<router-link to="/management/devices" class="card-compact bg-base-100 shadow-xl">
<div class="card-body">
<h5 class="card-title">View Active Sessions</h5>
<div>
Expand All @@ -56,7 +57,7 @@ const toast = useToast()
</div>
</div>
</router-link>
<router-link to="/registration" class="card-compact bg-base-100 shadow-xl">
<router-link to="/management/registration_tokens" class="card-compact bg-base-100 shadow-xl">
<div class="card-body">
<h5 class="card-title">Registration Tokens</h5>
<div>
Expand Down
4 changes: 2 additions & 2 deletions adminui2/src/pages/Groups.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ const isLoading = computed(() => {
const filterText = ref('')
const allRules = computed(() => groupsData.value ?? [])
const allGroups = computed(() => groupsData.value ?? [])
const filteredGroups = computed(() => {
const arr = allRules.value
const arr = allGroups.value
if (filterText.value == '') {
return arr
Expand Down
72 changes: 69 additions & 3 deletions adminui2/src/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import { ref, watch, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { useToastError } from '@/composables/useToastError'
import HrOr from '@/components/HrOr.vue'
import { useAuthStore } from '@/stores/auth'
import { Icons } from '@/util/icons'
import { changePassword } from '@/api'
const toast = useToast()
const authStore = useAuthStore()
const router = useRouter()
const toast = useToast()
const { hasCompletedAuth, loginError, isLoginLoading, loggedInUser } = storeToRefs(authStore)
enum ActiveScreens {
FirstStep,
PasswordChange,
Done
}
Expand All @@ -26,6 +28,10 @@ const activeScreen = computed(() => {
return ActiveScreens.FirstStep
}
if(loggedInUser.value.change) {
return ActiveScreens.PasswordChange
}
return ActiveScreens.Done
})
Expand Down Expand Up @@ -59,13 +65,46 @@ const cardTitle = computed(() => {
case ActiveScreens.FirstStep:
return 'Login to Wag'
case ActiveScreens.PasswordChange:
return 'Set a new password'
case ActiveScreens.Done:
return 'You have successfully logged in!'
default:
return ''
}
})
const { catcher } = useToastError()
const isPasswordChangeLoading = ref(false)
const newPassword = ref('')
async function doPasswordChange(event: Event) {
if (event) {
event.preventDefault()
}
try {
isPasswordChangeLoading.value = true
const res = await changePassword({
current_password: password.value,
new_password: newPassword.value
})
if (res.success) {
toast.success('Password changed successfully')
} else {
toast.warning('Failed to change password: ' + res.message)
}
authStore.refreshAuth()
} catch (e: any) {
catcher(e, 'Failed to change temporary password. ')
} finally {
isPasswordChangeLoading.value = false
}
}
</script>

<template>
Expand All @@ -77,7 +116,7 @@ const cardTitle = computed(() => {
</div>

<div v-if="activeScreen == ActiveScreens.FirstStep">
<form @submit="doCredentialLogin" v-if="true">
<form @submit="doCredentialLogin">
<div v-if="loginError != null" class="my-2 text-center text-red-500">
<p>{{ loginError }}</p>
</div>
Expand Down Expand Up @@ -108,6 +147,33 @@ const cardTitle = computed(() => {
</div>
</div>

<div v-if="activeScreen == ActiveScreens.PasswordChange">
<p class="text-center">You are required to change your password</p>
<form @submit="doPasswordChange">
<div class="form-control">
<label class="label">
<span class="label-text">Old Password</span>
</label>
<input type="password" placeholder="hunter2" class="input input-bordered" v-model="password" />
</div>
<div class="form-control">
<label class="label">
<span class="label-text">New Password</span>
</label>
<input type="password" placeholder="hunter2" class="input input-bordered" v-model="newPassword" />
</div>
<div v-if="loginError != null" class="mt-4 text-center text-red-500">
<p>{{ loginError }}</p>
</div>
<div class="form-control mt-6">
<button type="submit" class="btn btn-primary" :disabled="isPasswordChangeLoading">
<span class="loading loading-spinner loading-md" v-if="isLoginLoading"></span>
Change Password
</button>
</div>
</form>
</div>

<div v-if="activeScreen == ActiveScreens.Done" class="text-center">
<p>Welcome</p>
<font-awesome-icon :icon="Icons.Tick" class="my-8 text-success" style="font-size: 5rem" />
Expand Down
2 changes: 1 addition & 1 deletion adminui2/src/stores/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const useAuthStore = defineStore({
isLoggedIn: state => state.whoamiDetails?.user != null,
loggedInUser: state => state.whoamiDetails?.user,

hasCompletedAuth: state => state.whoamiDetails?.user != null,
hasCompletedAuth: state => state.whoamiDetails?.user != null && !state.whoamiDetails.user.change,

username: state => state.whoamiDetails?.user.username,

Expand Down
Loading

0 comments on commit 0446dbc

Please sign in to comment.