Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
derevnjuk committed Oct 2, 2021
2 parents 1003483 + 7297d30 commit b682f6e
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 47 deletions.
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
[
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion public/src/api/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function postSubscriptions(email: string): Promise<any> {

export function postUser(data: RegistrationUser): Promise<any> {
return makeApiRequest({
url: ApiUrl.Users,
url: `${ApiUrl.Users}/${data.op}`,
method: 'post',
data
});
Expand Down
33 changes: 21 additions & 12 deletions public/src/api/makeApiRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@ export function makeApiRequest<T>(
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'
};
});
}
1 change: 1 addition & 0 deletions public/src/interfaces/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface RegistrationUser {
lastName: string;
firstName: string;
password?: string;
op?: LoginFormMode;
}
4 changes: 0 additions & 4 deletions public/src/pages/auth/Login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
38 changes: 34 additions & 4 deletions public/src/pages/auth/Register/Register.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -9,30 +9,60 @@ const defaultUser: RegistrationUser = {
email: '',
firstName: '',
lastName: '',
password: ''
password: '',
op: LoginFormMode.BASIC
};

export const Register: FC = () => {
const [form, setForm] = useState<RegistrationUser>(defaultUser);
const { email, firstName, lastName, password } = form;

const [regResponse, setRegResponse] = useState<RegistrationUser | null>();
const [errorText, setErrorText] = useState<string | null>();

const [authMode, setAuthMode] = useState<LoginFormMode>(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 (
<AuthLayout>
<div className="login-form">
<form onSubmit={sendUser}>
<div className="form-group">
<label>Registration Type</label>
<select
className="form-control"
name="op"
placeholder="Authentication Type"
value={authMode}
onChange={onAuthModeChange}
>
<option value={LoginFormMode.BASIC}>
Simple REST-based Registration
</option>
<option value={LoginFormMode.OIDC}>
Simple OIDC-based Registration
</option>
</select>
</div>
<div className="form-group">
<label>First name</label>
<input
Expand Down Expand Up @@ -68,7 +98,7 @@ export const Register: FC = () => {
onInput={onInput}
/>
</div>

{errorText && <div className="error-text">{errorText}</div>}
<div className="form-group">
<label>Password</label>
<input
Expand Down
27 changes: 25 additions & 2 deletions src/keycloak/keycloak.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as jwkToPem from 'jwk-to-pem';
import { JWK } from 'jwk-to-pem';
import { stringify } from 'querystring';
import { verify } from 'jsonwebtoken';
import { User } from 'src/model/user.entity';

export interface OIDCIdentityConfig {
issuer?: string;
Expand All @@ -28,6 +29,10 @@ export interface RegisterUserData {
password: string;
}

export interface ExistingUserData {
email: string;
}

export interface GenerateTokenData {
username: string;
password: string;
Expand Down Expand Up @@ -137,17 +142,35 @@ export class KeyCloakService implements OnModuleInit {
});
}

public async isUserExists({ email }: ExistingUserData): Promise<boolean> {
this.log.debug(`Called isUserExist`);

const { access_token, token_type } = await this.generateToken();

const [existingUser]: unknown[] = 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<void> {
}: RegisterUserData): Promise<User> {
this.log.debug(`Called registerUser`);

const { access_token, token_type } = await this.generateToken();

await this.httpClient.post(
return this.httpClient.post(
`${this.server_uri}/admin/realms/${this.realm}/users`,
{
firstName,
Expand Down
1 change: 1 addition & 0 deletions src/users/api/CreateUserRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import { UserDto } from './UserDto';
export class CreateUserRequest extends UserDto {
@ApiProperty()
password: string;
op: string;
}
13 changes: 6 additions & 7 deletions src/users/api/UserDto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { User } from '../../model/user.entity';

export class UserDto {
@ApiProperty()
Expand All @@ -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);
}
}
62 changes: 48 additions & 14 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Controller,
Get,
Header,
HttpException,
HttpStatus,
InternalServerErrorException,
Logger,
Expand Down Expand Up @@ -59,7 +60,7 @@ export class UsersController {
async getUser(@Param('email') email: string): Promise<UserDto> {
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,
Expand Down Expand Up @@ -150,10 +151,10 @@ export class UsersController {
throw new NotFoundException('User not found in ldap');
}

return users.map<UserDto>(UserDto.convertToApi);
return users.map((user: User) => new UserDto(user));
}

@Post()
@Post('/basic')
@ApiOperation({
description: 'creates user',
})
Expand All @@ -163,30 +164,63 @@ export class UsersController {
})
async createUser(@Body() user: CreateUserRequest): Promise<UserDto> {
try {
this.logger.debug(`Create a user: ${user}`);
this.logger.debug(`Create a basic user: ${user}`);

const newUser = UserDto.convertToApi(
const userExists = await this.usersService.findByEmail(user.email);

if (userExists) {
throw new HttpException('User already exists', 409);
}

return new UserDto(
await this.usersService.createUser(
user.email,
user.firstName,
user.lastName,
user.password,
),
);
} 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<UserDto> {
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,
});

return newUser;
if (userExists) {
throw new HttpException('User already exists', 409);
}

return new UserDto(
await this.keyCloakService.registerUser({
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
password: user.password,
}),
);
} catch (err) {
throw new InternalServerErrorException({
error: err.message,
location: __filename,
});
throw new HttpException(
err.message ?? 'Something went wrong',
err.status ?? 500,
);
}
}

Expand Down

0 comments on commit b682f6e

Please sign in to comment.