feat: password reset, email change with verification, and password change

Password reset: multi-app support with custom notification linking to correct
frontend (app/portal/admin). Email change: self-service with password
confirmation and admin-initiated, both sending verification to new address
with 24h expiry. Confirmation sent to old email on completion. Password
change: authenticated endpoint revoking other sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 15:38:54 +02:00
parent 53100d4f6d
commit 836cffa232
42 changed files with 2643 additions and 67 deletions

View File

@@ -0,0 +1,61 @@
import { useMutation } from '@tanstack/vue-query'
import type { Ref } from 'vue'
import { apiClient } from '@/lib/axios'
interface ApiResponse<T> {
success: boolean
data: T
message: string
}
export interface ChangePasswordPayload {
current_password: string
password: string
password_confirmation: string
}
export interface ChangeEmailPayload {
new_email: string
password: string
app: 'app' | 'portal' | 'admin'
}
export interface AdminChangeEmailPayload {
new_email: string
}
export function useChangePassword() {
return useMutation({
mutationFn: async (payload: ChangePasswordPayload) => {
const { data } = await apiClient.post<ApiResponse<null>>(
'/me/change-password',
payload,
)
return data
},
})
}
export function useChangeEmail() {
return useMutation({
mutationFn: async (payload: ChangeEmailPayload) => {
const { data } = await apiClient.post<ApiResponse<null>>(
'/me/change-email',
payload,
)
return data
},
})
}
export function useAdminChangeEmail(orgId: Ref<string>) {
return useMutation({
mutationFn: async ({ userId, newEmail }: { userId: string; newEmail: string }) => {
const { data } = await apiClient.post<ApiResponse<null>>(
`/organisations/${orgId.value}/members/${userId}/change-email`,
{ new_email: newEmail },
)
return data
},
})
}