From a845783ebb5e5efc3c59c049e29a140daa705a07 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Fri, 9 Feb 2024 12:04:57 +0100 Subject: [PATCH] asd --- src/gql/user-authenticators.graphql | 7 +++++ src/routes/user/index.ts | 36 +++++++++++++++++++++- src/routes/user/user.ts | 20 ++++++++++++ src/routes/user/webauthn.ts | 28 +++++++++++++++++ src/utils/__generated__/graphql-request.ts | 18 +++++++++++ 5 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/gql/user-authenticators.graphql b/src/gql/user-authenticators.graphql index 1fbeef960..e529989d7 100644 --- a/src/gql/user-authenticators.graphql +++ b/src/gql/user-authenticators.graphql @@ -43,3 +43,10 @@ mutation updateUserSecurityKey($id: uuid!, $counter: bigint!) { id } } + +mutation deleteUserSecurityKey($id: uuid!, $userId: uuid!) { + deleteAuthUserSecurityKeys(where: {id: {_eq: $id}, userId: {_eq: $userId}}) { + affected_rows + } +} + diff --git a/src/routes/user/index.ts b/src/routes/user/index.ts index 3054d0913..c397f74f6 100644 --- a/src/routes/user/index.ts +++ b/src/routes/user/index.ts @@ -5,7 +5,7 @@ import { bodyValidator } from '@/validation'; import { authenticationGate } from '@/middleware/auth'; import { userMFAHandler, userMfaSchema } from './mfa'; -import { userHandler } from './user'; +import { userHandler, deleteUserHandler } from './user'; import { userPasswordHandler, userPasswordSchema } from './password'; import { userPasswordResetHandler, @@ -26,6 +26,8 @@ import { addSecurityKeyHandler, addSecurityKeyVerifyHandler, userVerifyAddSecurityKeySchema, + userDeleteSecurityKeySchema, + deleteSecurityKeyVerifyHandler, } from './webauthn'; const router = Router(); @@ -44,6 +46,21 @@ router.get( aw(userHandler), ); +/** + * POST /user/delete + * @summary Deletes the authenticated user + * @return {string} 200 - The user has been successfully deleted - application/json + * @return {InvalidRequestError} 400 - The payload is invalid - application/json + * @return {UnauthenticatedUserError} 401 - User is not authenticated - application/json + * @security BearerAuth + * @tags User management + */ +router.post( + '/user/delete', + authenticationGate(true), + aw(deleteUserHandler), +); + /** * POST /user/password/reset * @summary Send an email asking the user to reset their password @@ -188,5 +205,22 @@ router.post( aw(addSecurityKeyVerifyHandler) ); +/** + * POST /user/webauthn/delete + * @summary Deletes a webauthn security key + * @param {DeleteSecurityKeySchema} request.body.required + * @return {string} 200 - The security key has been successfully deleted - application/json + * @return {InvalidRequestError} 400 - The payload is invalid - application/json + * @return {UnauthenticatedUserError} 401 - User is not authenticated - application/json + * @security BearerAuth + * @tags User management + */ +router.post( + '/user/webauthn/delete', + bodyValidator(userDeleteSecurityKeySchema), + authenticationGate(true), + aw(deleteSecurityKeyVerifyHandler) +); + const userRouter = router; export { userRouter }; diff --git a/src/routes/user/user.ts b/src/routes/user/user.ts index 7718c1686..0c4fa5115 100644 --- a/src/routes/user/user.ts +++ b/src/routes/user/user.ts @@ -1,6 +1,10 @@ import { RequestHandler } from 'express'; +import { ReasonPhrases } from 'http-status-codes'; + +import { sendError } from '@/errors'; import { getUser } from '@/utils'; +import { gqlSdk } from '../../utils/gql-sdk'; export const userHandler: RequestHandler = async ( req, @@ -14,3 +18,19 @@ export const userHandler: RequestHandler = async ( ...user, }); }; + +export const deleteUserHandler: RequestHandler = async ( + req, + res +): Promise => { + const { userId } = req.auth as RequestAuth; + + + try { + await gqlSdk.deleteUser({ userId }); + } catch (e) { + return sendError(res, 'invalid-request'); + } + + return res.json(ReasonPhrases.OK); +}; diff --git a/src/routes/user/webauthn.ts b/src/routes/user/webauthn.ts index e8943c4a4..1c4864f84 100644 --- a/src/routes/user/webauthn.ts +++ b/src/routes/user/webauthn.ts @@ -1,4 +1,5 @@ import { RequestHandler } from 'express'; +import { ReasonPhrases } from 'http-status-codes'; import { generateRegistrationOptions } from '@simplewebauthn/server'; import { @@ -103,3 +104,30 @@ export const addSecurityKeyVerifyHandler: RequestHandler< return sendUnspecifiedError(res, e); } }; + +export type DeleteSecurityKeyRequestBody = { + id: string; +}; + +export const userDeleteSecurityKeySchema = + Joi.object({ + id: Joi.string().required(), + }).meta({ className: 'DeleteSecurityKeySchema' }); + +export const deleteSecurityKeyVerifyHandler: RequestHandler< + {}, + {}, + DeleteSecurityKeyRequestBody +> = async (req, res) => { + + const resp = await gqlSdk.deleteUserSecurityKey({ + id: req.body.id, + userId: req.auth?.userId, + }); + + if (!resp.deleteAuthUserSecurityKeys?.affected_rows) { + return sendError(res, 'invalid-request'); + } + + return res.json(ReasonPhrases.OK); +}; diff --git a/src/utils/__generated__/graphql-request.ts b/src/utils/__generated__/graphql-request.ts index 27f05a81b..8c5b6a0db 100644 --- a/src/utils/__generated__/graphql-request.ts +++ b/src/utils/__generated__/graphql-request.ts @@ -3977,6 +3977,14 @@ export type UpdateUserSecurityKeyMutationVariables = Exact<{ export type UpdateUserSecurityKeyMutation = { __typename?: 'mutation_root', updateAuthUserSecurityKey?: { __typename?: 'authUserSecurityKeys', id: any } | null }; +export type DeleteUserSecurityKeyMutationVariables = Exact<{ + id: Scalars['uuid']; + userId: Scalars['uuid']; +}>; + + +export type DeleteUserSecurityKeyMutation = { __typename?: 'mutation_root', deleteAuthUserSecurityKeys?: { __typename?: 'authUserSecurityKeys_mutation_response', affected_rows: number } | null }; + export type AuthUserProvidersQueryVariables = Exact<{ provider: Scalars['String']; providerUserId: Scalars['String']; @@ -4270,6 +4278,13 @@ export const UpdateUserSecurityKeyDocument = gql` } } `; +export const DeleteUserSecurityKeyDocument = gql` + mutation deleteUserSecurityKey($id: uuid!, $userId: uuid!) { + deleteAuthUserSecurityKeys(where: {id: {_eq: $id}, userId: {_eq: $userId}}) { + affected_rows + } +} + `; export const AuthUserProvidersDocument = gql` query authUserProviders($provider: String!, $providerUserId: String!) { authUserProviders( @@ -4506,6 +4521,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = updateUserSecurityKey(variables: UpdateUserSecurityKeyMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(UpdateUserSecurityKeyDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'updateUserSecurityKey', 'mutation'); }, + deleteUserSecurityKey(variables: DeleteUserSecurityKeyMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(DeleteUserSecurityKeyDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteUserSecurityKey', 'mutation'); + }, authUserProviders(variables: AuthUserProvidersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(AuthUserProvidersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'authUserProviders', 'query'); },