From 22d60943e2301778b799ce3d9ce1f4c0c5772b84 Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 2 Sep 2021 15:32:09 +0300 Subject: [PATCH 1/4] build(docker-compose): set `PROXY_ADDRESS_FORWARDING` to true (#78) --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 81c3fcda..c0aec05e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -104,6 +104,7 @@ services: KEYCLOAK_USER: admin KEYCLOAK_PASSWORD: Pa55w0rd KEYCLOAK_IMPORT: /opt/jboss/keycloak/imports/realm-export.json -Dkeycloak.profile.feature.upload_scripts=enabled + PROXY_ADDRESS_FORWARDING: 'true' healthcheck: test: [ From ad8db6130c340902c4716643db79d7f93a26fd10 Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 29 Sep 2021 16:51:12 +0300 Subject: [PATCH 2/4] fix(*): status codes returning from backend after registration (#80) Backend returning status code 500 after a successful registration --- public/src/api/httpClient.ts | 2 +- public/src/api/makeApiRequest.ts | 33 +++++++----- public/src/interfaces/User.ts | 1 + public/src/pages/auth/Login/Login.tsx | 4 -- public/src/pages/auth/Register/Register.tsx | 38 ++++++++++++-- src/keycloak/keycloak.service.ts | 32 ++++++++++-- src/users/api/CreateUserRequest.ts | 1 + src/users/users.controller.ts | 57 ++++++++++++++++----- 8 files changed, 131 insertions(+), 37 deletions(-) diff --git a/public/src/api/httpClient.ts b/public/src/api/httpClient.ts index 8882249e..c8eda47e 100644 --- a/public/src/api/httpClient.ts +++ b/public/src/api/httpClient.ts @@ -51,7 +51,7 @@ export function postSubscriptions(email: string): Promise { export function postUser(data: RegistrationUser): Promise { return makeApiRequest({ - url: ApiUrl.Users, + url: `${ApiUrl.Users}/${data.op}`, method: 'post', data }); diff --git a/public/src/api/makeApiRequest.ts b/public/src/api/makeApiRequest.ts index e1b9b8ec..ba6941b3 100644 --- a/public/src/api/makeApiRequest.ts +++ b/public/src/api/makeApiRequest.ts @@ -15,18 +15,27 @@ export function makeApiRequest( return response.data; }) .catch((error) => { - if (error.response.status === 401) { - sessionStorage.removeItem('email'); - sessionStorage.removeItem('token'); - return { - ...error, - errorText: - 'Authentication failed, please check your credentials and try again' - }; + switch (error.response.status) { + case 401: + sessionStorage.removeItem('email'); + sessionStorage.removeItem('token'); + return { + ...error, + errorText: + 'Authentication failed, please check your credentials and try again' + }; + break; + case 409: + return { + ...error, + errorText: 'User already exists' + }; + break; + default: + return { + ...error, + errorText: 'Something went wrong. Please try again later' + }; } - return { - ...error, - errorText: 'Something went wrong. Please try again later' - }; }); } diff --git a/public/src/interfaces/User.ts b/public/src/interfaces/User.ts index 9598f008..2eb339ca 100644 --- a/public/src/interfaces/User.ts +++ b/public/src/interfaces/User.ts @@ -25,4 +25,5 @@ export interface RegistrationUser { lastName: string; firstName: string; password?: string; + op?: LoginFormMode; } diff --git a/public/src/pages/auth/Login/Login.tsx b/public/src/pages/auth/Login/Login.tsx index 301806b9..1a811639 100644 --- a/public/src/pages/auth/Login/Login.tsx +++ b/public/src/pages/auth/Login/Login.tsx @@ -58,10 +58,6 @@ export const Login: FC = () => { const { value } = target as HTMLSelectElement & { value: LoginFormMode }; setForm({ ...form, op: value }); setMode(value); - switch (value as LoginFormMode) { - default: - return; - } }; const sendUser = (e: FormEvent) => { diff --git a/public/src/pages/auth/Register/Register.tsx b/public/src/pages/auth/Register/Register.tsx index 205192a9..909e7b61 100644 --- a/public/src/pages/auth/Register/Register.tsx +++ b/public/src/pages/auth/Register/Register.tsx @@ -1,6 +1,6 @@ import React, { FC, FormEvent, useState } from 'react'; import { postUser } from '../../../api/httpClient'; -import { RegistrationUser } from '../../../interfaces/User'; +import { RegistrationUser, LoginFormMode } from '../../../interfaces/User'; import AuthLayout from '../AuthLayout'; import { Link } from 'react-router-dom'; import showRegResponse from './showRegReponse'; @@ -9,7 +9,8 @@ const defaultUser: RegistrationUser = { email: '', firstName: '', lastName: '', - password: '' + password: '', + op: LoginFormMode.BASIC }; export const Register: FC = () => { @@ -17,22 +18,51 @@ export const Register: FC = () => { const { email, firstName, lastName, password } = form; const [regResponse, setRegResponse] = useState(); + const [errorText, setErrorText] = useState(); + + const [authMode, setAuthMode] = useState(LoginFormMode.BASIC); const onInput = ({ target }: { target: EventTarget | null }) => { const { name, value } = target as HTMLInputElement; setForm({ ...form, [name]: value }); }; + const onAuthModeChange = ({ target }: { target: EventTarget | null }) => { + const { value } = target as HTMLSelectElement & { value: LoginFormMode }; + setForm({ ...form, op: value }); + setAuthMode(value); + }; + const sendUser = (e: FormEvent) => { e.preventDefault(); - postUser(form).then((data) => setRegResponse(data)); + postUser(form).then((data) => { + setRegResponse(data); + setErrorText(data.errorText); + }); }; return (
+
+ + +
{ onInput={onInput} />
- + {errorText &&
{errorText}
}
{ + this.log.debug(`Called isUserExist`); + + const { access_token, token_type } = await this.generateToken(); + + const [existingUser] = await this.httpClient.get( + `${this.server_uri}/admin/realms/${this.realm}/users?email=${email}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `${token_type} ${access_token}`, + }, + responseType: 'json', + } + ); + return !!existingUser; + } + public async registerUser({ firstName, lastName, email, password, - }: RegisterUserData): Promise { + }: RegisterUserData): Promise { this.log.debug(`Called registerUser`); const { access_token, token_type } = await this.generateToken(); - await this.httpClient.post( + const user: User = await this.httpClient.post( `${this.server_uri}/admin/realms/${this.realm}/users`, { firstName, @@ -170,7 +195,8 @@ export class KeyCloakService implements OnModuleInit { }, responseType: 'json', }, - ); + ) + return user; } public async generateToken(tokenData?: GenerateTokenData): Promise { diff --git a/src/users/api/CreateUserRequest.ts b/src/users/api/CreateUserRequest.ts index a6e6dbd2..37a123ea 100644 --- a/src/users/api/CreateUserRequest.ts +++ b/src/users/api/CreateUserRequest.ts @@ -4,4 +4,5 @@ import { UserDto } from './UserDto'; export class CreateUserRequest extends UserDto { @ApiProperty() password: string; + op: string; } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index b66a9adf..390ef1bb 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -3,6 +3,7 @@ import { Controller, Get, Header, + HttpException, HttpStatus, InternalServerErrorException, Logger, @@ -153,7 +154,7 @@ export class UsersController { return users.map(UserDto.convertToApi); } - @Post() + @Post('/basic') @ApiOperation({ description: 'creates user', }) @@ -163,8 +164,14 @@ export class UsersController { }) async createUser(@Body() user: CreateUserRequest): Promise { try { - this.logger.debug(`Create a user: ${user}`); - + this.logger.debug(`Create a basic user: ${user}`); + const userExists = await this.usersService.findByEmail(user.email); + if (userExists) { + throw new HttpException( + 'User already exists', + 409 + ) + } const newUser = UserDto.convertToApi( await this.usersService.createUser( user.email, @@ -173,20 +180,44 @@ export class UsersController { user.password, ), ); + return newUser; + } catch (err) { + throw new HttpException(err.message ?? 'Something went wrong', err.status ?? 500) + } + } + + @Post('/oidc') + @ApiOperation({ + description: 'creates user', + }) + @ApiResponse({ + type: UserDto, + status: 200, + }) + async createOIDCUser(@Body() user: CreateUserRequest): Promise { + try { + this.logger.debug(`Create a OIDC user: ${user}`); - await this.keyCloakService.registerUser({ + const userExists = await this.keyCloakService.isUserExists({ email: user.email, - firstName: user.firstName, - lastName: user.lastName, - password: user.password, }); - + if (userExists) { + throw new HttpException( + 'User already exists', + 409 + ) + } + const newUser = UserDto.convertToApi( + await this.keyCloakService.registerUser({ + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + password: user.password, + })); return newUser; - } catch (err) { - throw new InternalServerErrorException({ - error: err.message, - location: __filename, - }); + + } catch ({ response = 'Something went wrong', status = 500 }) { + throw new HttpException(response, status) } } From 7e61100faad5e5f64db07850156ca6f9be3bfe8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Sep 2021 16:51:38 +0300 Subject: [PATCH 3/4] build(deps): bump tmpl from 1.0.4 to 1.0.5 (#79) Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5. - [Release notes](https://github.com/daaku/nodejs-tmpl/releases) - [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5) --- updated-dependencies: - dependency-name: tmpl dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83a9f865..f1267b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9298,9 +9298,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-fast-properties": { From 7297d30af17abeb38751786f31e779f35a394d10 Mon Sep 17 00:00:00 2001 From: Artem Derevnjuk Date: Wed, 29 Sep 2021 18:33:27 +0300 Subject: [PATCH 4/4] fix(auth): compile-time errors (#124) --- src/keycloak/keycloak.service.ts | 17 ++++++------- src/users/api/UserDto.ts | 13 +++++----- src/users/users.controller.ts | 43 +++++++++++++++++--------------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/keycloak/keycloak.service.ts b/src/keycloak/keycloak.service.ts index d065f119..54735776 100644 --- a/src/keycloak/keycloak.service.ts +++ b/src/keycloak/keycloak.service.ts @@ -30,7 +30,7 @@ export interface RegisterUserData { } export interface ExistingUserData { - email: string, + email: string; } export interface GenerateTokenData { @@ -142,14 +142,12 @@ export class KeyCloakService implements OnModuleInit { }); } - public async isUserExists({ - email, - }: ExistingUserData): Promise { - this.log.debug(`Called isUserExist`); + public async isUserExists({ email }: ExistingUserData): Promise { + this.log.debug(`Called isUserExist`); const { access_token, token_type } = await this.generateToken(); - const [existingUser] = await this.httpClient.get( + const [existingUser]: unknown[] = await this.httpClient.get( `${this.server_uri}/admin/realms/${this.realm}/users?email=${email}`, { headers: { @@ -157,7 +155,7 @@ export class KeyCloakService implements OnModuleInit { Authorization: `${token_type} ${access_token}`, }, responseType: 'json', - } + }, ); return !!existingUser; } @@ -172,7 +170,7 @@ export class KeyCloakService implements OnModuleInit { const { access_token, token_type } = await this.generateToken(); - const user: User = await this.httpClient.post( + return this.httpClient.post( `${this.server_uri}/admin/realms/${this.realm}/users`, { firstName, @@ -195,8 +193,7 @@ export class KeyCloakService implements OnModuleInit { }, responseType: 'json', }, - ) - return user; + ); } public async generateToken(tokenData?: GenerateTokenData): Promise { diff --git a/src/users/api/UserDto.ts b/src/users/api/UserDto.ts index 99ffe965..50c999a4 100644 --- a/src/users/api/UserDto.ts +++ b/src/users/api/UserDto.ts @@ -1,5 +1,4 @@ import { ApiProperty } from '@nestjs/swagger'; -import { User } from '../../model/user.entity'; export class UserDto { @ApiProperty() @@ -11,11 +10,11 @@ export class UserDto { @ApiProperty() lastName: string; - public static convertToApi(user: User): UserDto { - return { - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }; + constructor( + params: { + [P in keyof UserDto]: UserDto[P]; + }, + ) { + Object.assign(this, params); } } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 390ef1bb..8255756a 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -60,7 +60,7 @@ export class UsersController { async getUser(@Param('email') email: string): Promise { try { this.logger.debug(`Find a user by email: ${email}`); - return UserDto.convertToApi(await this.usersService.findByEmail(email)); + return new UserDto(await this.usersService.findByEmail(email)); } catch (err) { throw new InternalServerErrorException({ error: err.message, @@ -151,7 +151,7 @@ export class UsersController { throw new NotFoundException('User not found in ldap'); } - return users.map(UserDto.convertToApi); + return users.map((user: User) => new UserDto(user)); } @Post('/basic') @@ -165,14 +165,14 @@ export class UsersController { async createUser(@Body() user: CreateUserRequest): Promise { try { this.logger.debug(`Create a basic user: ${user}`); + const userExists = await this.usersService.findByEmail(user.email); + if (userExists) { - throw new HttpException( - 'User already exists', - 409 - ) + throw new HttpException('User already exists', 409); } - const newUser = UserDto.convertToApi( + + return new UserDto( await this.usersService.createUser( user.email, user.firstName, @@ -180,9 +180,11 @@ export class UsersController { user.password, ), ); - return newUser; } catch (err) { - throw new HttpException(err.message ?? 'Something went wrong', err.status ?? 500) + throw new HttpException( + err.message ?? 'Something went wrong', + err.status ?? 500, + ); } } @@ -194,30 +196,31 @@ export class UsersController { type: UserDto, status: 200, }) - async createOIDCUser(@Body() user: CreateUserRequest): Promise { + async createOIDCUser(@Body() user: CreateUserRequest): Promise { try { this.logger.debug(`Create a OIDC user: ${user}`); const userExists = await this.keyCloakService.isUserExists({ email: user.email, }); + if (userExists) { - throw new HttpException( - 'User already exists', - 409 - ) + throw new HttpException('User already exists', 409); } - const newUser = UserDto.convertToApi( + + return new UserDto( await this.keyCloakService.registerUser({ email: user.email, firstName: user.firstName, lastName: user.lastName, password: user.password, - })); - return newUser; - - } catch ({ response = 'Something went wrong', status = 500 }) { - throw new HttpException(response, status) + }), + ); + } catch (err) { + throw new HttpException( + err.message ?? 'Something went wrong', + err.status ?? 500, + ); } }