🗃️ Move from file system to database
This commit is contained in:
parent
61c62738b5
commit
c07d177a24
@ -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"
|
||||
|
||||
@ -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<UserEntity> 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<UserEntity> 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()));
|
||||
persistentUser.setPassword(BcryptUtil.bcryptHash(password));
|
||||
userRepo.persist(persistentUser);
|
||||
return;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new WebApplicationException("failed_to_write_to_file", 500);
|
||||
}
|
||||
}
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
throw new BadRequestException("no_password_provided");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RolesAllowed("admin")
|
||||
public List<User> getUsers() throws IOException
|
||||
{
|
||||
return userRepo.findAll().stream().map(user -> new User(user.username(), user.email(), user.roles(), null, user.initial())).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<User> userOptional = userRepo.findOptionalByUsername(login.username());
|
||||
Optional<UserEntity> 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();
|
||||
|
||||
@ -2,6 +2,6 @@ package dev.dinauer.login;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record User(String username, String email, Set<String> roles, String password, Boolean initial)
|
||||
public record User(String username, String email, Set<String> roles, String password)
|
||||
{
|
||||
}
|
||||
|
||||
119
src/main/java/dev/dinauer/login/UserEntity.java
Normal file
119
src/main/java/dev/dinauer/login/UserEntity.java
Normal file
@ -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<String> getRoles()
|
||||
{
|
||||
if (this.roles != null)
|
||||
{
|
||||
return Set.of(this.roles.split(","));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public UserEntity setRoles(Set<String> roles)
|
||||
{
|
||||
if (roles != null)
|
||||
{
|
||||
this.roles = String.join(",", roles);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -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<UserEntity, String>
|
||||
{
|
||||
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<UserEntity> findOptionalByUsername(String username)
|
||||
{
|
||||
File file = new File(getFilePathForUsername(username));
|
||||
LOG.info("Query user from file {}", file.getAbsolutePath());
|
||||
return getUserFromFile(file);
|
||||
}
|
||||
|
||||
public Optional<User> findOptionalByUsername(String username) throws IOException
|
||||
{
|
||||
File file = new File(getFilePathForUsername(username));
|
||||
LOG.info("Query user from file {}", file.getAbsolutePath());
|
||||
return getOptionalUserFromFile(file);
|
||||
}
|
||||
|
||||
public List<User> findAll() throws IOException
|
||||
{
|
||||
try(Stream<Path> paths = Files.list(workdirProvider.getWorkdirPath(Path.of("users"))))
|
||||
{
|
||||
List<User> 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<User> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
%dev,test.quarkus.hibernate-orm.schema-management.strategy=drop-and-create
|
||||
Loading…
x
Reference in New Issue
Block a user