From ac4624043cfbdf145465004ce31d96c2f547b8ef Mon Sep 17 00:00:00 2001 From: ZerNico Date: Tue, 30 May 2023 16:53:23 +0200 Subject: [PATCH] add password changing --- apps/api/src/logto/management-api.ts | 30 ++++++ apps/api/src/trpc/middlewares/logto.ts | 8 +- apps/api/src/trpc/routes/user/index.ts | 34 +++++- apps/web/components/ui/Button.vue | 7 +- apps/web/components/ui/Card.vue | 13 ++- apps/web/components/ui/IconButton.vue | 14 +++ apps/web/components/ui/Input.vue | 7 ++ apps/web/components/ui/NavBar.vue | 2 +- apps/web/layouts/auth.vue | 2 +- apps/web/lib/utils/api.ts | 6 +- apps/web/locales/de-DE.json | 20 +++- apps/web/locales/en-US.json | 20 +++- apps/web/pages/index.vue | 2 +- .../index.vue} | 61 ++++++----- apps/web/pages/user/edit-profile/password.vue | 100 ++++++++++++++++++ 15 files changed, 285 insertions(+), 41 deletions(-) create mode 100644 apps/web/components/ui/IconButton.vue rename apps/web/pages/user/{edit-profile.vue => edit-profile/index.vue} (57%) create mode 100644 apps/web/pages/user/edit-profile/password.vue diff --git a/apps/api/src/logto/management-api.ts b/apps/api/src/logto/management-api.ts index ecc6729e..4f65f5bb 100644 --- a/apps/api/src/logto/management-api.ts +++ b/apps/api/src/logto/management-api.ts @@ -45,4 +45,34 @@ export class ManagementApiClient { }) return response } + + public async verifyPassword(userId: string, password: string) { + const response = await ofetch(joinURL(env.LOGTO_URL, 'api', 'users', userId, 'password', 'verify'), { + method: 'POST', + headers: { + Authorization: `Bearer ${await this.getToken()}`, + }, + body: { + password, + }, + }).catch((err) => { + throw new LogtoError(err.data) + }) + return response + } + + public async updatePassword(userId: string, password: string) { + const response = await ofetch(joinURL(env.LOGTO_URL, 'api', 'users', userId, 'password'), { + method: 'PATCH', + headers: { + Authorization: `Bearer ${await this.getToken()}`, + }, + body: { + password: password, + }, + }).catch((err) => { + throw new LogtoError(err.data) + }) + return response + } } diff --git a/apps/api/src/trpc/middlewares/logto.ts b/apps/api/src/trpc/middlewares/logto.ts index 634eafef..00755eb5 100644 --- a/apps/api/src/trpc/middlewares/logto.ts +++ b/apps/api/src/trpc/middlewares/logto.ts @@ -2,15 +2,17 @@ import { TRPCError } from '@trpc/server' import { middleware, publicProcedure } from '../trpc' import { verifyLogtoJwt } from '../../logto/jwt' +const INVALID_TOKEN_MESSAGE = 'error.invalid_token' + const isLogtoAuthed = middleware(async ({ ctx, next }) => { const token = ctx.req.headers.authorization?.split(' ')[1] - if (!token) throw new TRPCError({ code: 'UNAUTHORIZED' }) + if (!token) throw new TRPCError({ code: 'UNAUTHORIZED', message: INVALID_TOKEN_MESSAGE }) const payload = await verifyLogtoJwt(token).catch(() => { - throw new TRPCError({ code: 'UNAUTHORIZED' }) + throw new TRPCError({ code: 'UNAUTHORIZED', message: INVALID_TOKEN_MESSAGE }) }) - if (!payload.sub) throw new TRPCError({ code: 'UNAUTHORIZED' }) + if (!payload.sub) throw new TRPCError({ code: 'UNAUTHORIZED', message: INVALID_TOKEN_MESSAGE }) const user = await ctx.prisma.user.findUnique({ where: { diff --git a/apps/api/src/trpc/routes/user/index.ts b/apps/api/src/trpc/routes/user/index.ts index eec70c66..b12ea707 100644 --- a/apps/api/src/trpc/routes/user/index.ts +++ b/apps/api/src/trpc/routes/user/index.ts @@ -64,7 +64,7 @@ export const userRouter = router({ } } - throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', cause: err }) + throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'error.unknown_error', cause: err }) }) const user = await ctx.prisma.user.update({ @@ -81,4 +81,36 @@ export const userRouter = router({ user, } }), + + updatePassword: logtoAuthedProcedure + .input( + z + .object({ + currentPassword: z.string(), + newPassword: z + .string() + .min(9, { message: 'error.password_too_short' }) + .max(8196, { message: 'error.password_too_long' }), + newPasswordConfirm: z.string(), + }) + .refine((data) => data.newPassword === data.newPasswordConfirm, { + message: 'error.passwords_do_not_match', + path: ['confirmPassword'], + }) + ) + .mutation(async ({ ctx, input }) => { + try { + await ctx.mm.verifyPassword(ctx.user.id, input.currentPassword) + await ctx.mm.updatePassword(ctx.user.id, input.newPassword) + } catch (err) { + if (err instanceof LogtoError && err.code === 'session.invalid_credentials') { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'error.password_incorrect' }) + } + throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'error.unknown_error', cause: err }) + } + + return { + success: true, + } + }), }) diff --git a/apps/web/components/ui/Button.vue b/apps/web/components/ui/Button.vue index 1563b2b1..32792c53 100644 --- a/apps/web/components/ui/Button.vue +++ b/apps/web/components/ui/Button.vue @@ -1,10 +1,11 @@