Skip to content

Commit

Permalink
feat: add deleteUserAccount endpoint (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter committed Aug 16, 2024
1 parent ba375f7 commit 4c7b8c8
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 11 deletions.
58 changes: 50 additions & 8 deletions src/account/account.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
import { AccountController } from './account.controller';
import { HttpService } from '@nestjs/axios';
import { User } from '../auth/user.dto';
import { of } from 'rxjs';
import { of, throwError } from "rxjs";
import { NotFoundException } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { KeycloakService } from './keycloak.service';
import { KeycloakUser } from './keycloak-user.dto';

describe('AccountController', () => {
let controller: AccountController;
const mockHttp = {
put: jest.fn().mockReturnValue(of({ data: undefined })),
get: jest.fn().mockReturnValue(of({ data: undefined })),
post: jest.fn().mockReturnValue(of({ date: undefined })),
delete: jest.fn().mockReturnValue(of({ date: undefined })),
};

let mockHttp;

const user: User = {
sub: 'user-id',
realm: 'ndb-dev',
Expand All @@ -24,6 +21,15 @@ describe('AccountController', () => {
};

beforeEach(async () => {
mockHttp = {
put: jest.fn().mockReturnValue(of({ data: undefined })),
get: jest.fn().mockReturnValue(of({ data: undefined })),
post: jest.fn().mockReturnValue(of({ date: undefined })),
delete: jest.fn().mockReturnValue(of({ date: undefined })),
}

jest.clearAllMocks();

const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule],
controllers: [AccountController],
Expand All @@ -34,7 +40,6 @@ describe('AccountController', () => {
}).compile();

controller = module.get<AccountController>(AccountController);
jest.clearAllMocks();
});

it('should be defined', () => {
Expand Down Expand Up @@ -70,6 +75,43 @@ describe('AccountController', () => {
});
});

it('should delete return 200 with bodyDeleted true', (done) => {
mockHttp.delete.mockReturnValue(of(""));
const id = 'my-id';

controller
.deleteAccount({ user }, id )
.subscribe({
next: (value) => {
expect(value.userDeleted).toBeTruthy()
// delete user
expect(mockHttp.delete).toHaveBeenCalledWith(
expect.stringContaining("/ndb-dev/users/my-id"),
);
done()
}
});
});

it('should delete return 200 with bodyDeleted false', (done) => {
mockHttp.delete.mockReturnValue(throwError(() => new Error()));
const id = 'my-id';

controller
.deleteAccount({ user }, id )
.subscribe({
next: (value) => {
expect(value.userDeleted).toBeFalsy()
// delete user
expect(mockHttp.delete).toHaveBeenCalledWith(
expect.stringContaining("/ndb-dev/users/my-id"),
);
done()
}
})
;
});

it('should return a user with the assigned roles', async () => {
const requestedUser: KeycloakUser = { username: 'my-user', id: 'user-id' };
const roles = ['user-role-1', 'user-role-2'];
Expand Down
41 changes: 39 additions & 2 deletions src/account/account.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
Headers,
Param,
Expand All @@ -12,13 +13,16 @@ import {
import { BearerGuard } from '../auth/bearer/bearer.guard';
import { ApiBearerAuth, ApiHeader, ApiOperation } from '@nestjs/swagger';
import {
catchError,
concatMap,
concatWith,
firstValueFrom,
last,
Observable,
tap,
} from 'rxjs';
of,
switchMap,
tap
} from "rxjs";
import { ForgotEmailReq } from './forgot-email-req.dto';
import { SetEmailReq } from './set-email-req.dto';
import { User } from '../auth/user.dto';
Expand Down Expand Up @@ -149,6 +153,39 @@ export class AccountController {
);
}

@ApiOperation({
summary: 'delete an user account',
description:
'Looks if an account with given id exist in realm and deletes it',
})
@ApiBearerAuth()
@ApiHeader({ name: 'Accept-Language', required: false })
@UseGuards(BearerGuard, RolesGuard)
@Roles(AccountController.ACCOUNT_MANAGEMENT_ROLE)
@Delete('/:userId')
deleteAccount(
@Req() req,
@Param('userId') userId: string,
) {
const user = req.user as User;

return this.keycloak.deleteUser(
user.realm,
userId
).pipe(
switchMap(() => {
return of({
userDeleted: true
});
}),
catchError(() => {
return of({
userDeleted: false
});
}),
)
}

@ApiOperation({
summary: 'get account details',
description:
Expand Down
15 changes: 14 additions & 1 deletion src/account/keycloak.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { KeycloakUser } from './keycloak-user.dto';
import { map, Observable } from 'rxjs';
import { catchError, map, Observable, of } from 'rxjs';

/**
* This service provides endpoints for interacting with Keycloak.
Expand All @@ -17,6 +17,7 @@ export class KeycloakService {
];

private readonly keycloakUrl: string;

constructor(private http: HttpService, configService: ConfigService) {
this.keycloakUrl = configService.get('KEYCLOAK_URL');
}
Expand All @@ -35,6 +36,18 @@ export class KeycloakService {
);
}

/**
* Delete a user in the realm with the given id
* @param realm
* @param id
*/
deleteUser(realm: string, id: string) {
return this.perform(
this.http.delete,
`${realm}/users/${id}`,
)
}

/**
* Update the user with the given id in the realm.
* @param realm
Expand Down

0 comments on commit 4c7b8c8

Please sign in to comment.