diff --git a/Dockerfile b/Dockerfile index 114cd8a..e64b609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,8 +105,6 @@ COPY target/quarkus-app/quarkus/ /deployments/quarkus/ EXPOSE 8080 -RUN sudo chmod -R 777 /var/lib/kubooboo - USER quarkus ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0" diff --git a/src/main/java/dev/dinauer/UserResource.java b/src/main/java/dev/dinauer/UserResource.java index f7a7fdb..1edddfc 100644 --- a/src/main/java/dev/dinauer/UserResource.java +++ b/src/main/java/dev/dinauer/UserResource.java @@ -1,18 +1,20 @@ package dev.dinauer; import dev.dinauer.login.User; +import dev.dinauer.login.UserEntity; import dev.dinauer.login.UserRepo; import io.quarkus.elytron.security.common.BcryptUtil; import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.io.IOException; -import java.util.List; +import java.util.Optional; +import java.util.Set; @Path("/users") @ApplicationScoped @@ -28,52 +30,50 @@ public class UserResource @GET @Produces(MediaType.APPLICATION_JSON) @Path("/{username}") - public User getUser(@PathParam("username") String username) throws IOException + public User getUser(@PathParam("username") String id) { - User persistentUser = userRepo.findByUsername(username); - return new User(persistentUser.username(), persistentUser.email(), persistentUser.roles(), null, null); + Optional userOptional = userRepo.findByIdOptional(id); + if (userOptional.isPresent()) + { + UserEntity user = userOptional.get(); + return new User(user.getUsername(), user.getEmail(), user.getRoles(), null); + } + throw new NotFoundException(); } @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - public User createUser(User user) throws IOException + @Transactional + public void createUser(User user) { - userRepo.persist(new User(user.username(), user.email(), user.roles(), BcryptUtil.bcryptHash(user.password()), false)); - return new User(user.username(), user.email(), user.roles(), null, false); + UserEntity userEntity = UserEntity.init(); + userEntity.setUsername(user.username()); + userEntity.setPassword(BcryptUtil.bcryptHash(user.password())); + userEntity.setRoles(Set.of("user")); + userEntity.setEmail(user.email()); + userRepo.persist(userEntity); } @PUT @Path("/{username}/password") @Produces @Consumes(MediaType.TEXT_PLAIN) + @Transactional public void changePassword(@PathParam("username") String username, String password) throws IOException { - User persistentUser = userRepo.findByUsername(username); - if(password != null && !password.isBlank()) + Optional persistentUserOptional = userRepo.findOptionalByUsername(username); + if(persistentUserOptional.isPresent() && password != null && !password.isBlank()) { - if(securityIdentity.getPrincipal().getName().equals(persistentUser.username())) + UserEntity persistentUser = persistentUserOptional.get(); + if(securityIdentity.getPrincipal().getName().equals(persistentUser.getUsername())) { - try - { - userRepo.persist(new User(persistentUser.username(), persistentUser.email(), persistentUser.roles(), BcryptUtil.bcryptHash(password), persistentUser.initial())); - return; - } - catch (IOException e) - { - throw new WebApplicationException("failed_to_write_to_file", 500); - } + persistentUser.setPassword(BcryptUtil.bcryptHash(password)); + userRepo.persist(persistentUser); + return; } throw new ForbiddenException(); } throw new BadRequestException("no_password_provided"); } - - @GET - @Produces(MediaType.APPLICATION_JSON) - @RolesAllowed("admin") - public List getUsers() throws IOException - { - return userRepo.findAll().stream().map(user -> new User(user.username(), user.email(), user.roles(), null, user.initial())).toList(); - } } diff --git a/src/main/java/dev/dinauer/login/LoginResource.java b/src/main/java/dev/dinauer/login/LoginResource.java index 932c182..c1cf493 100644 --- a/src/main/java/dev/dinauer/login/LoginResource.java +++ b/src/main/java/dev/dinauer/login/LoginResource.java @@ -25,15 +25,15 @@ public class LoginResource @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) - public String login(Login login) throws IOException + public String login(Login login) { - Optional userOptional = userRepo.findOptionalByUsername(login.username()); + Optional userOptional = userRepo.findOptionalByUsername(login.username()); if(userOptional.isPresent()) { - User user = userOptional.get(); - if(BcryptUtil.matches(login.password(), user.password())) + UserEntity user = userOptional.get(); + if(BcryptUtil.matches(login.password(), user.getPassword())) { - return Jwt.upn(user.username()).expiresAt(ZonedDateTime.now().plusDays(15).toInstant()).groups(user.roles()).sign(); + return Jwt.upn(user.getId()).expiresAt(ZonedDateTime.now().plusDays(15).toInstant()).groups(user.getRoles()).sign(); } LOG.info("Cannot access user. Forbidden"); throw new ForbiddenException(); diff --git a/src/main/java/dev/dinauer/login/User.java b/src/main/java/dev/dinauer/login/User.java index 137376e..c2af912 100644 --- a/src/main/java/dev/dinauer/login/User.java +++ b/src/main/java/dev/dinauer/login/User.java @@ -2,6 +2,6 @@ package dev.dinauer.login; import java.util.Set; -public record User(String username, String email, Set roles, String password, Boolean initial) +public record User(String username, String email, Set roles, String password) { } diff --git a/src/main/java/dev/dinauer/login/UserEntity.java b/src/main/java/dev/dinauer/login/UserEntity.java new file mode 100644 index 0000000..7601f33 --- /dev/null +++ b/src/main/java/dev/dinauer/login/UserEntity.java @@ -0,0 +1,119 @@ +package dev.dinauer.login; + +import jakarta.persistence.*; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Entity +@Table(name = "kubooboo_user") +public class UserEntity +{ + @Id + private String id; + + private String username; + + private String firstname; + + private String lastname; + + @Column(name = "user_password") + private String password; + + private String email; + + private String roles; + + public static UserEntity init() + { + UserEntity user = new UserEntity(); + user.setId(UUID.randomUUID().toString()); + return user; + } + + public String getId() + { + return id; + } + + public UserEntity setId(String id) + { + this.id = id; + return this; + } + + public String getUsername() + { + return username; + } + + public UserEntity setUsername(String username) + { + this.username = username; + return this; + } + + public String getFirstname() + { + return firstname; + } + + public UserEntity setFirstname(String firstname) + { + this.firstname = firstname; + return this; + } + + public String getLastname() + { + return lastname; + } + + public UserEntity setLastname(String lastname) + { + this.lastname = lastname; + return this; + } + + public String getPassword() + { + return password; + } + + public UserEntity setPassword(String password) + { + this.password = password; + return this; + } + + public String getEmail() + { + return email; + } + + public UserEntity setEmail(String email) + { + this.email = email; + return this; + } + + public Set getRoles() + { + if (this.roles != null) + { + return Set.of(this.roles.split(",")); + } + return null; + } + + public UserEntity setRoles(Set roles) + { + if (roles != null) + { + this.roles = String.join(",", roles); + } + return this; + } +} diff --git a/src/main/java/dev/dinauer/login/UserRepo.java b/src/main/java/dev/dinauer/login/UserRepo.java index 13ab2d9..3f6671c 100644 --- a/src/main/java/dev/dinauer/login/UserRepo.java +++ b/src/main/java/dev/dinauer/login/UserRepo.java @@ -1,88 +1,17 @@ package dev.dinauer.login; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.dinauer.WorkdirProvider; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.NotFoundException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; -import java.util.stream.Stream; @ApplicationScoped -public class UserRepo +public class UserRepo implements PanacheRepositoryBase { - private static final Logger LOG = LoggerFactory.getLogger(UserRepo.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - @Inject - WorkdirProvider workdirProvider; - - public User findByUsername(String username) throws IOException + public Optional findOptionalByUsername(String username) { - File file = new File(getFilePathForUsername(username)); - LOG.info("Query user from file {}", file.getAbsolutePath()); - return getUserFromFile(file); - } - - public Optional findOptionalByUsername(String username) throws IOException - { - File file = new File(getFilePathForUsername(username)); - LOG.info("Query user from file {}", file.getAbsolutePath()); - return getOptionalUserFromFile(file); - } - - public List findAll() throws IOException - { - try(Stream paths = Files.list(workdirProvider.getWorkdirPath(Path.of("users")))) - { - List result = new ArrayList<>(); - for(Path path : paths.toList()) - { - result.add(getUserFromFile(new File(path.toString()))); - } - return result; - } - } - - private User getUserFromFile(File file) throws IOException - { - if(file.exists()) - { - return OBJECT_MAPPER.readValue(file, User.class); - } - throw new NotFoundException("Did not find file " + file.getAbsolutePath()); - } - - private Optional getOptionalUserFromFile(File file) throws IOException - { - if(file.exists()) - { - return Optional.of(OBJECT_MAPPER.readValue(file, User.class)); - } - return Optional.empty(); - } - - private String getFilePathForUsername(String username) - { - return workdirProvider.getWorkdir(Path.of("users", String.format("%s.json", username))); - } - - public void persist(User user) throws IOException - { - try(FileWriter fw = new FileWriter(getFilePathForUsername(user.username()), false)) - { - fw.write(OBJECT_MAPPER.writeValueAsString(user)); - } + return find("username = :username", Parameters.with("username", username)).firstResultOptional(); } } diff --git a/src/main/java/dev/dinauer/utils/StartupService.java b/src/main/java/dev/dinauer/utils/StartupService.java index 8ba6fcd..578cc79 100644 --- a/src/main/java/dev/dinauer/utils/StartupService.java +++ b/src/main/java/dev/dinauer/utils/StartupService.java @@ -1,12 +1,15 @@ package dev.dinauer.utils; import dev.dinauer.login.User; +import dev.dinauer.login.UserEntity; import dev.dinauer.login.UserRepo; import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.runtime.Startup; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import org.jboss.logging.Logger; import java.io.IOException; @@ -26,17 +29,23 @@ public class StartupService UserRepo userRepo; @PostConstruct - void init() throws IOException + public void init() { if(userRepo.findOptionalByUsername(INITIAL_USERNAME).isEmpty()) { + QuarkusTransaction.begin(); userRepo.persist(buildInitialUser()); + QuarkusTransaction.commit(); LOG.infof("Initialized user 'admin'"); } } - private static User buildInitialUser() + private static UserEntity buildInitialUser() { - return new User(INITIAL_USERNAME, null, Set.of("admin"), BcryptUtil.bcryptHash(INITIAL_PASSWORD), true); + UserEntity initialUser = UserEntity.init(); + initialUser.setUsername(INITIAL_USERNAME); + initialUser.setPassword(BcryptUtil.bcryptHash(INITIAL_PASSWORD)); + initialUser.setRoles(Set.of("admin")); + return initialUser; } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8989bbc..400ca73 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,4 +20,4 @@ dev.dinauer.kubooboo.work.dir=/var/lib/kubooboo/work %dev.quarkus.datasource.username = postgres %dev.quarkus.datasource.password = postgres %dev.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:6666/postgres -%dev,test.quarkus.hibernate-orm.schema-management.strategy = none \ No newline at end of file +%dev,test.quarkus.hibernate-orm.schema-management.strategy=drop-and-create \ No newline at end of file