Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(user-registration): user registration backend + frontend #78

Merged
merged 5 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions client/src/pages/registration/api/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { SERVER_URL } from "shared/config";
import { API_REGISTRATION } from "shared/config/backend";

interface RegistrationRequestBody {
name: string;
surname: string;
fullName: string;
login: string;
password: string;
}
Expand All @@ -18,7 +17,7 @@ export interface RegistrationAction {
export const action: ActionFunction = async ({
request,
}): Promise<RegistrationAction> => {
const { name, surname, login, password, errors } = parseForm(
const { fullName, login, password, errors } = parseForm(
await request.formData(),
);

Expand All @@ -29,8 +28,7 @@ export const action: ActionFunction = async ({
}

const registrationRequestBody: RegistrationRequestBody = {
name: name,
surname: surname,
fullName: fullName,
login: login,
password: password,
};
Expand All @@ -48,6 +46,6 @@ export const action: ActionFunction = async ({
}

return {
errors: [`Failed to authenticate: '${status}' code`],
errors: [`User already exists`],
};
};
24 changes: 9 additions & 15 deletions client/src/pages/registration/model/parseForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,34 @@ import { MINIMAL_PASSWORD_LENGTH } from "shared/config/frontend";
export function parseForm(data: FormData) {
const errors = [];

const name = data.get("name");
if (typeof name !== "string" || name === "") {
const fullName = data.get("fullName");
if (typeof fullName !== "string" || fullName.trim() === "") {
errors.push("Type name at form");
}

const surname = data.get("surname");
if (typeof surname !== "string" || surname === "") {
errors.push("Type surname at form");
}

const login = data.get("login");
if (typeof login !== "string" || login === "") {
if (typeof login !== "string" || login.trim() === "") {
errors.push("Type login at form");
}

if (typeof login === "string" && login.length < 4) {
errors.push("Login must greater than 3 symbols");
if (typeof login === "string" && login.trim() === "") {
errors.push("Type login at form");
}

const password = data.get("password");
if (typeof password !== "string" || password === "") {
if (typeof password !== "string" || password.trim() === "") {
errors.push("Type password at form");
}

if (
typeof password === "string" &&
password.length < MINIMAL_PASSWORD_LENGTH
) {
errors.push(`Password must greater ${MINIMAL_PASSWORD_LENGTH} symbols`);
errors.push(`Password length less than ${MINIMAL_PASSWORD_LENGTH}`);
}

return { name, surname, login, password, errors } as {
name: string;
surname: string;
return { fullName, login, password, errors } as {
fullName: string;
login: string;
password: string;
errors: string[];
Expand Down
3 changes: 1 addition & 2 deletions client/src/pages/registration/ui/RegistrationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export const RegistrationPage = () => {
)}

<Form method="POST">
<input type="text" name="name" placeholder={"Name"} />
<input type="text" name="surname" placeholder={"Surname"} />
<input type="text" name="fullName" placeholder={"Full Name"} />
<input type="text" name="login" placeholder={"Login"} />
<input type="password" name="password" placeholder={"Password"} />
<input type="submit" value="Registration" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ import mu.muse.domain.instrument.Country
import mu.muse.domain.instrument.Instrument
import mu.muse.domain.instrument.InstrumentId
import mu.muse.domain.instrument.InstrumentName
import mu.muse.domain.instrument.ManufacturerDate
import mu.muse.domain.instrument.Manufacturer
import mu.muse.domain.instrument.ManufacturerDate
import mu.muse.domain.instrument.Material
import mu.muse.domain.instrument.ReleaseDate
import mu.muse.domain.user.FullName
import mu.muse.domain.user.Password
import mu.muse.domain.user.Role
import mu.muse.domain.user.User
import mu.muse.domain.user.UserId
import mu.muse.domain.user.Username
import mu.muse.domain.user.UsernameId
import mu.muse.persistence.instrument.InMemoryInstrumentIdGenerator
import mu.muse.persistence.instrument.InMemoryInstrumentRepository
import mu.muse.persistence.user.InMemoryUserRepository
import mu.muse.persistence.user.InMemoryUsernameIdGenerator
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import java.time.Instant

@Configuration
Expand All @@ -30,23 +31,21 @@ class PersistenceConfiguration {
fun usernameIdGenerator() = InMemoryUsernameIdGenerator()

@Bean
fun users(idGenerator: IdGenerator<UsernameId>): Set<User> {
val passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()

fun users(idGenerator: IdGenerator<UserId>, passwordEncoder: PasswordEncoder): Set<User> {
val user = User.create(
idGenerator = idGenerator,
id = idGenerator.generate(),
username = Username.from("user"),
password = Password.from(passwordEncoder.encode("123")),
role = Role.user(),
fullName = "User Userov",
fullName = FullName.from("User Userov"),
)

val editor = User.create(
idGenerator = idGenerator,
id = idGenerator.generate(),
username = Username.from("editor"),
password = Password.from(passwordEncoder.encode("321")),
role = Role.editor(),
fullName = "Editor Editorov",
fullName = FullName.from("Editor Editorov"),
)

return setOf(user, editor)
Expand All @@ -60,7 +59,6 @@ class PersistenceConfiguration {

@Bean
fun instruments(idGenerator: IdGenerator<InstrumentId>): Set<Instrument> {

val releasedGuitar = Instrument.create(
id = idGenerator.generate(),
name = InstrumentName.from("Fender Stratocaster"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import mu.muse.rest.instruments.GetInstrumentMaterialsEndpoint
import mu.muse.rest.instruments.GetInstrumentTypesEndpoint
import mu.muse.rest.login.BasicLoginEndpoint
import mu.muse.rest.profile.GetProfileEndpoint
import mu.muse.rest.registration.RegistrationEndpoint
import mu.muse.usecase.BasicLogin
import mu.muse.usecase.CreateInstrument
import mu.muse.usecase.DeleteInstrumentById
Expand All @@ -24,6 +25,7 @@ import mu.muse.usecase.GetInstrumentMaterials
import mu.muse.usecase.GetInstrumentTypes
import mu.muse.usecase.GetInstrumentsByCriteria
import mu.muse.usecase.GetProfile
import mu.muse.usecase.RegisterUser
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

Expand Down Expand Up @@ -73,4 +75,7 @@ class RestConfiguration {

@Bean
fun editInstrumentEndpoint(editInstrument: EditInstrument) = EditInstrumentEndpoint(editInstrument)

@Bean
fun registrationEndpoint(registerUser: RegisterUser) = RegistrationEndpoint(registerUser)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import mu.muse.rest.API_INSTRUMENTS
import mu.muse.rest.API_INSTRUMENT_BY_ID
import mu.muse.rest.API_INSTRUMENT_MATERIALS
import mu.muse.rest.API_INSTRUMENT_TYPES
import mu.muse.rest.API_REGISTRATION
import mu.muse.rest.AUTH_BASIC_LOGIN
import mu.muse.usecase.access.user.UserExtractor
import mu.muse.usecase.scenario.BasicLoginUseCase
import mu.muse.usecase.scenario.login.BasicLoginUseCase
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -26,6 +27,7 @@ import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
Expand All @@ -34,10 +36,10 @@ import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import java.security.Key


@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
@Suppress("TooManyFunctions")
class SecurityConfiguration {

@Bean
Expand Down Expand Up @@ -88,6 +90,7 @@ class SecurityConfiguration {
.requestMatchers(HttpMethod.GET, API_INSTRUMENT_MATERIALS).permitAll()
.requestMatchers(HttpMethod.GET, API_COUNTRIES).permitAll()
.requestMatchers(HttpMethod.GET, API_GET_MANUFACTURER_NAMES).permitAll()
.requestMatchers(HttpMethod.POST, API_REGISTRATION).permitAll()
.anyRequest().authenticated()
}

Expand Down Expand Up @@ -135,4 +138,7 @@ class SecurityConfiguration {

@Bean
fun securityContextHolderAwareRequestFilter() = SecurityContextHolderAwareRequestFilter()

@Bean
fun passwordEncoder() = BCryptPasswordEncoder()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package mu.muse.application.muse

import mu.muse.domain.IdGenerator
import mu.muse.domain.instrument.InstrumentId
import mu.muse.domain.user.UserId
import mu.muse.usecase.access.instrument.InstrumentExtractor
import mu.muse.usecase.access.instrument.InstrumentPersister
import mu.muse.usecase.access.instrument.InstrumentRemover
import mu.muse.usecase.access.user.UserExtractor
import mu.muse.usecase.access.user.UserPersister
import mu.muse.usecase.scenario.country.GetCountriesUseCase
import mu.muse.usecase.scenario.instrument.CreateInstrumentUseCase
import mu.muse.usecase.scenario.instrument.DeleteInstrumentByIdUseCase
Expand All @@ -17,8 +19,10 @@ import mu.muse.usecase.scenario.instrument.GetInstrumentTypesUseCase
import mu.muse.usecase.scenario.instrument.GetInstrumentsByCriteriaPaginatedUseCase
import mu.muse.usecase.scenario.instrument.GetInstrumentsByCriteriaUseCase
import mu.muse.usecase.scenario.profile.GetProfileUseCase
import mu.muse.usecase.scenario.registration.RegisterUserUseCase
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.crypto.password.PasswordEncoder

@Suppress("TooManyFunctions")
@Configuration
Expand Down Expand Up @@ -62,4 +66,13 @@ class UseCaseConfiguration {
@Bean
fun editInstrument(instrumentExtractor: InstrumentExtractor, instrumentPersister: InstrumentPersister) =
EditInstrumentUseCase(instrumentExtractor, instrumentPersister)

@Bean
fun registerUser(
idGenerator: IdGenerator<UserId>,
userExtractor: UserExtractor,
userPersister: UserPersister,
passwordEncoder: PasswordEncoder,
) =
RegisterUserUseCase(idGenerator, userExtractor, userPersister, passwordEncoder)
}
16 changes: 16 additions & 0 deletions server/app/src/main/kotlin/mu/muse/domain/user/FullName.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mu.muse.domain.user

import mu.muse.common.annotations.ValueObject

@ValueObject
data class FullName(private val value: String) {

fun toStringValue() = value

companion object {
fun from(fullName: String): FullName {
require(fullName.isNotEmpty())
return FullName(fullName)
}
}
}
11 changes: 4 additions & 7 deletions server/app/src/main/kotlin/mu/muse/domain/user/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@ package mu.muse.domain.user

import mu.muse.common.types.AggregateRoot
import mu.muse.common.types.Version
import mu.muse.domain.IdGenerator

typealias FullName = String

class User internal constructor(
id: UsernameId,
id: UserId,
val username: Username,
val password: Password,
val role: Role,
val fullName: FullName,
version: Version,
) : AggregateRoot<UsernameId>(id, version) {
) : AggregateRoot<UserId>(id, version) {

companion object {
fun create(
idGenerator: IdGenerator<UsernameId>,
id: UserId,
username: Username,
password: Password,
role: Role,
fullName: FullName,
): User {
return User(
id = idGenerator.generate(),
id = id,
username = username,
password = password,
role = role,
Expand Down
10 changes: 10 additions & 0 deletions server/app/src/main/kotlin/mu/muse/domain/user/UserId.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mu.muse.domain.user

class UserId internal constructor(private val value: Long) {
companion object {
fun from(value: Long): UserId {
require(value >= 1L && value <= Long.MAX_VALUE)
return UserId(value)
}
}
}
10 changes: 0 additions & 10 deletions server/app/src/main/kotlin/mu/muse/domain/user/UsernameId.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package mu.muse.persistence.user

import mu.muse.domain.IdGenerator
import mu.muse.domain.user.UsernameId
import mu.muse.domain.user.UserId
import java.util.concurrent.atomic.AtomicLong

class InMemoryUsernameIdGenerator : IdGenerator<UsernameId> {
class InMemoryUsernameIdGenerator : IdGenerator<UserId> {

private val counter = AtomicLong(0L)

override fun generate(): UsernameId {
return UsernameId.from(counter.incrementAndGet())
override fun generate(): UserId {
return UserId.from(counter.incrementAndGet())
}
}
1 change: 1 addition & 0 deletions server/app/src/main/kotlin/mu/muse/rest/EndpointURL.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ const val API_COUNTRIES = "$API/countries"
const val API_GET_MANUFACTURER_NAMES = "$API/manufacturers"
const val API_CREATE_INSTRUMENT = "$API/instrument/create"
const val API_EDIT_INSTRUMENT = "$API/instrument/edit"
const val API_REGISTRATION = "$API/registration"
Loading
Loading