diff --git a/app/src/components/ChangePasswordModal.vue b/app/src/components/ChangePasswordModal.vue new file mode 100644 index 00000000..12dae57d --- /dev/null +++ b/app/src/components/ChangePasswordModal.vue @@ -0,0 +1,116 @@ + + + + + ✕ + Byt lösenord + + + + + Nuvarande lösenord + + + + + + + Nytt lösenord + + + + + + + Bekräfta nytt lösenord + + + + Lösenorden stämmer inte överens. + + + + + + Avbryt + + + Spara + + + + + + + + \ No newline at end of file diff --git a/app/src/components/layout/DefaultHeader.vue b/app/src/components/layout/DefaultHeader.vue index 011ba766..37fc5f4b 100644 --- a/app/src/components/layout/DefaultHeader.vue +++ b/app/src/components/layout/DefaultHeader.vue @@ -1,4 +1,10 @@ + + + @@ -34,6 +40,15 @@ {{ translateUserRole(authStore.role) }} --> + + + password + + logout @@ -50,6 +65,9 @@ {{ route.label }} + + Ändra lösenord + {{ isAtLeastUser ? 'Logga ut' : 'Avsluta' }} @@ -64,9 +82,25 @@ import { useAuthStore } from '@/stores/authStore'; import { hasAtLeastSecurityRole, translateUserRole } from 'schemas'; import { computed, ref } from 'vue'; import unsplashBackground from '@/assets/milad-fakurian-DX7pT_guAyE-unsplash.jpg'; +import ChangePasswordModal from '@/components/ChangePasswordModal.vue'; const router = useRouter(); const authStore = useAuthStore(); +const showPasswordModal = ref(false); + +async function handlePasswordSubmit(payload: { + currentPassword: string; + newPassword: string; +}) { + await authStore.changePassword(payload); + console.log('Password changed'); + showPasswordModal.value = false; +} + +const changePasswordHamburgerMenu = async () => { + showPasswordModal.value = true; + closeMenu(); +} const isAtLeastUser = computed(() => { return authStore.role ? hasAtLeastSecurityRole(authStore.role, 'user') : false; diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index 7f748a48..57c35f6f 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -18,6 +18,11 @@ if (localMode) { console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); +interface ChangePasswordRequest { + currentPassword: string; + newPassword: string; +} + export function createUser(username: string, password: string, role: UserRole) { return handleResponse(() => authEndpoint.post('/user/create', { role: role.toString(), @@ -189,6 +194,22 @@ export const loginWithAutoToken = async (username: string, password: string) => return latestJwtToken; }; +export const changePassword = async (data: ChangePasswordRequest) => { + try { + const currentPassword = data.currentPassword; + const newPassword = data.newPassword; + + await authEndpoint.post('/user/change-Password', { + currentPassword, + newPassword, + }) + + return Promise.resolve(); + } catch (e) { + return Promise.reject(Error(e.response.data)); + } +}; + // export const deleteUser = (uuid: string) => authEndpoint.post('delete-user', { uuid }); // export const createUser = (payload: { username: string, password: string, gathering?: string, role: NonGuestUserRole }) => handleResponse>(() => authEndpoint.post('create', payload)); // export const updateUser = (payload: { uuid: string, username?: string, password?: string, gathering?: string, role?: NonGuestUserRole }) => handleResponse>(() => authEndpoint.post('update', payload)); diff --git a/app/src/stores/authStore.ts b/app/src/stores/authStore.ts index 1c1dd97b..175515c4 100644 --- a/app/src/stores/authStore.ts +++ b/app/src/stores/authStore.ts @@ -1,9 +1,14 @@ import { defineStore } from 'pinia'; -import { logout as authLogout, login as authLogin, userAutoToken, guestAutoToken } from '@/modules/authClient'; +import { logout as authLogout, login as authLogin, userAutoToken, guestAutoToken, changePassword as authChangePassword } from '@/modules/authClient'; import { hasAtLeastSecurityRole, type JwtPayload } from 'schemas'; import jwtDecode from 'jwt-decode'; import { computed, ref } from 'vue'; +interface ChangePasswordRequest { + currentPassword: string; + newPassword: string; +} + const browserHasCookie = () => { const cookieValue = document.cookie .split('; ') @@ -84,6 +89,10 @@ export const useAuthStore = defineStore('auth', () => { }); } + async function changePassword(data: ChangePasswordRequest) { + authChangePassword(data); + } + return { isAuthenticated, isNotGuest: isLoggedIn, @@ -100,6 +109,7 @@ export const useAuthStore = defineStore('auth', () => { restoreFromSession, logout, login, + changePassword, routePrefix, }; }, { diff --git a/backend/auth/src/userRoutes.ts b/backend/auth/src/userRoutes.ts index 104d7ebc..b01c995a 100644 --- a/backend/auth/src/userRoutes.ts +++ b/backend/auth/src/userRoutes.ts @@ -203,6 +203,98 @@ const updateUser: RequestHandler = async (req: UpdateUserRequest, res) => { } }; +interface ChangePasswordRequest extends ExpressReq { + body: { + currentPassword: string; + newPassword: string; + } +} + +const updateUserPassword: RequestHandler = async (req: ChangePasswordRequest, res) => { + const userData = req.session.user; + const data = req.body; + try { + if (!userData) { + throw new Error('no client userdata. unauthorized!'); + } + + if (userData.role === "guest" || userData.role === "god") { + throw new Error('you have no role! Thus you are not authorized!'); + } + + if (!data) { + throw new Error('no input data sent!'); + } + } catch (e) { + const msg = extractMessageFromCatch(e, 'You give bad data!!!!'); + res.status(400).send(msg); + return; + } + + const currentUserDatabase = await db.query.users.findFirst({ + columns: { + userId: true, + role: true, + password: true, + }, + where: (users, { eq }) => eq(users.userId, userData.userId) + }) + + try { + if (!currentUserDatabase) { + throw new Error('User not found in databse.') + } + + if (userData.role !== currentUserDatabase.role) { + throw new Error('Current user data does not match database.') + } + } catch (e) { + const msg = extractMessageFromCatch(e, 'Not authorized!'); + res.status(401).send(msg); + return; + } + + try { + + if (!data.currentPassword) { + throw new Error('Inputted data does not match data in database!') + } + + const correct = await bcrypt.compare(data.currentPassword, currentUserDatabase.password); + if (!correct) { + throw new Error('Inputted data does not match data in database!') + } + } catch (e) { + const msg = extractMessageFromCatch(e, 'Not authorized!'); + res.status(401).send(msg); + return; + } + + let hashedPassword = undefined; + if (data.newPassword) { + hashedPassword = await bcrypt.hash(data.newPassword, 10); + } + + const userUpdate = { + password: hashedPassword, + }; + + try { + const [result] = await db.update(schema.users) + .set(userUpdate) + .where(eq(schema.users.userId, currentUserDatabase.userId)) + .returning(); + const resultWithoutPassword = exclude(result, 'password'); + res.status(201).send(resultWithoutPassword); + return; + } catch (e) { + const errorMsg = extractMessageFromCatch(e, 'failed to update user!'); + console.error(errorMsg); + res.status(501).send(errorMsg); + return; + } +} + interface DeleteUserRequest extends ExpressReq { body: { userId: UserId @@ -283,6 +375,34 @@ const getUsers: RequestHandler = async (req, res) => { res.send(dbResponse); } +// const getCurrentUser: RequestHandler = async (req, res) => { +// const userData = req.session.user; +// try { +// if (!userData) { +// throw new Error('no client userdata. unauthorized!'); +// } +// if (!userData.role) { +// throw new Error('you have no role! Thus you are not authorized!'); +// } +// } catch (e) { +// const msg = extractMessageFromCatch(e, 'You give bad data!!!!'); +// res.status(400).send(msg); +// return; +// } + +// const dbResponse = await db.query.users.findFirst({ +// columns: { +// userId: true, +// username: true, +// role: true, +// password: true, +// }, +// where: (users, { eq }) => eq(users.userId, userData.userId) +// }) + +// res.send(dbResponse); +// } + const getAdmins: RequestHandler = async (req, res) => { const userData = req.session.user; @@ -482,6 +602,8 @@ export default function createUserRouter(): Router { userRouter.post('/create-sender', isLoggedIn, createSenderForVenue); userRouter.get('/me', isLoggedIn, getSelf); + userRouter.post('/change-Password', isLoggedIn, updateUserPassword) + // userRouter.get('/get-current-user', isLoggedIn, getCurrentUser) userRouter.get('/jwt', isLoggedIn, getJwt);
+ Lösenorden stämmer inte överens. +