feat(auth): bootstrap admin on first boot + forced password change + admin reset
CI / Build Native (push) Has been cancelled
CI / Build Native (push) Has been cancelled
Backend changes (no frontend yet):
Schema (User entity)
- + mustChange_password (boolean NOT NULL, default false)
- + is_admin (boolean NOT NULL, default false)
Hibernate update mode adds both columns automatically.
BootstrapAdmin (new, ApplicationScoped, @Observes StartupEvent)
- runs only when User.count() == 0 and app.bootstrap.admin.enabled=true
- generates a 20-char random password (alphabet without 0/o/O/1/l/I)
- persists the user with isAdmin=true, mustChangePassword=true
- prints a banner to stdout AND to the JBoss logger so docker logs
picks it up:
BOOTSTRAP-ADMIN-USERNAME admin
BOOTSTRAP-ADMIN-PASSWORD <random>
BOOTSTRAP-ADMIN-CHANGE This password MUST be changed on first login ...
- idempotent: skips if any user already exists
MustChangePasswordFilter (new, @Provider ContainerRequestFilter)
- runs after JWT auth (Priorities.AUTHENTICATION + 100)
- for authenticated requests with mustChangePassword=true, returns
403 with {error, mustChangePassword:true} unless the path is
/api/auth/change-password or /api/auth/logout
Change-password endpoint (POST /api/auth/change-password)
- @Authenticated, body {currentPassword, newPassword}
- verifies currentPassword via bcrypt, validates newPassword>=8 chars,
updates hash and sets mustChangePassword=false
- returns updated AuthMeResponse and re-issues the auth cookie
Admin reset endpoint (POST /api/auth/admin/reset-password)
- @RolesAllowed("admin")
- body {username, newPassword}
- sets target's passwordHash and mustChangePassword=true (forces change
on next login)
- security: only users in the JWT 'admin' group can hit it; isAdmin
is stored on the user record so a stale token can't promote itself
JWT groups now include 'admin' for isAdmin users; previously everyone
was just 'user'.
Config (application.properties)
- app.bootstrap.admin.enabled=true
- app.bootstrap.admin.username=admin
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
package com.l2.shots.auth;
|
||||||
|
|
||||||
|
public class AdminResetPasswordRequest {
|
||||||
|
public String username;
|
||||||
|
public String newPassword;
|
||||||
|
}
|
||||||
@@ -7,12 +7,16 @@ public class AuthMeResponse {
|
|||||||
public UUID id;
|
public UUID id;
|
||||||
public String username;
|
public String username;
|
||||||
public Instant createdAt;
|
public Instant createdAt;
|
||||||
|
public boolean mustChangePassword;
|
||||||
|
public boolean isAdmin;
|
||||||
|
|
||||||
public AuthMeResponse() {}
|
public AuthMeResponse() {}
|
||||||
|
|
||||||
public AuthMeResponse(UUID id, String username, Instant createdAt) {
|
public AuthMeResponse(UUID id, String username, Instant createdAt, boolean mustChangePassword, boolean isAdmin) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
|
this.mustChangePassword = mustChangePassword;
|
||||||
|
this.isAdmin = isAdmin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.l2.shots.auth;
|
package com.l2.shots.auth;
|
||||||
|
|
||||||
import io.quarkus.security.Authenticated;
|
import io.quarkus.security.Authenticated;
|
||||||
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
@@ -16,6 +17,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|||||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Path("/api/auth")
|
@Path("/api/auth")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@@ -28,6 +30,9 @@ public class AuthResource {
|
|||||||
@Inject
|
@Inject
|
||||||
JwtCookieAuth jwtCookieAuth;
|
JwtCookieAuth jwtCookieAuth;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
JsonWebToken currentJwt;
|
||||||
|
|
||||||
@ConfigProperty(name = "app.auth.cookie-name")
|
@ConfigProperty(name = "app.auth.cookie-name")
|
||||||
String cookieName;
|
String cookieName;
|
||||||
|
|
||||||
@@ -74,6 +79,59 @@ public class AuthResource {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/change-password")
|
||||||
|
@Authenticated
|
||||||
|
public Response changePassword(@Context HttpHeaders headers, ChangePasswordRequest body) {
|
||||||
|
if (body == null || body.currentPassword == null || body.newPassword == null) {
|
||||||
|
return Response.status(400).entity(new ErrorBody("Faltan campos requeridos")).build();
|
||||||
|
}
|
||||||
|
Optional<JsonWebToken> jwt = jwtCookieAuth.extractToken(headers);
|
||||||
|
if (jwt.isEmpty()) return Response.status(401).build();
|
||||||
|
Optional<User> user = authService.getEntityFromToken(jwt.get());
|
||||||
|
if (user.isEmpty()) return Response.status(401).build();
|
||||||
|
AuthService.ChangePasswordResult result = authService.changePassword(
|
||||||
|
user.get().id, body.currentPassword, body.newPassword);
|
||||||
|
switch (result) {
|
||||||
|
case OK:
|
||||||
|
User refreshed = User.findById(user.get().id);
|
||||||
|
if (refreshed == null) return Response.status(401).build();
|
||||||
|
String newToken = authService.buildToken(refreshed.id);
|
||||||
|
return Response.ok(AuthService.toAuthMe(refreshed))
|
||||||
|
.cookie(buildAuthCookie(newToken))
|
||||||
|
.build();
|
||||||
|
case WRONG_CURRENT_PASSWORD:
|
||||||
|
return Response.status(401).entity(new ErrorBody("La contraseña actual es incorrecta")).build();
|
||||||
|
case WEAK_NEW_PASSWORD:
|
||||||
|
return Response.status(400).entity(new ErrorBody(
|
||||||
|
"La nueva contraseña debe tener al menos " + AuthService.MIN_PASSWORD_LENGTH + " caracteres")).build();
|
||||||
|
case NOT_FOUND:
|
||||||
|
return Response.status(401).build();
|
||||||
|
default:
|
||||||
|
return Response.status(500).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/admin/reset-password")
|
||||||
|
@RolesAllowed("admin")
|
||||||
|
public Response adminResetPassword(AdminResetPasswordRequest body) {
|
||||||
|
if (body == null || body.username == null || body.newPassword == null) {
|
||||||
|
return Response.status(400).entity(new ErrorBody("Faltan campos requeridos")).build();
|
||||||
|
}
|
||||||
|
UUID requesterId;
|
||||||
|
try {
|
||||||
|
requesterId = UUID.fromString(currentJwt.getSubject());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.status(401).build();
|
||||||
|
}
|
||||||
|
boolean ok = authService.resetPasswordAsAdmin(requesterId, body.username, body.newPassword);
|
||||||
|
if (!ok) {
|
||||||
|
return Response.status(400).entity(new ErrorBody("Usuario no encontrado o contraseña inválida")).build();
|
||||||
|
}
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/me")
|
@Path("/me")
|
||||||
public Response me(@Context HttpHeaders headers) {
|
public Response me(@Context HttpHeaders headers) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -20,6 +21,13 @@ public class AuthService {
|
|||||||
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,30}$");
|
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,30}$");
|
||||||
public static final int MIN_PASSWORD_LENGTH = 8;
|
public static final int MIN_PASSWORD_LENGTH = 8;
|
||||||
|
|
||||||
|
public enum ChangePasswordResult {
|
||||||
|
OK,
|
||||||
|
NOT_FOUND,
|
||||||
|
WRONG_CURRENT_PASSWORD,
|
||||||
|
WEAK_NEW_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
@ConfigProperty(name = "mp.jwt.verify.issuer")
|
@ConfigProperty(name = "mp.jwt.verify.issuer")
|
||||||
String issuer;
|
String issuer;
|
||||||
|
|
||||||
@@ -36,11 +44,26 @@ public class AuthService {
|
|||||||
user.username = username.toLowerCase();
|
user.username = username.toLowerCase();
|
||||||
user.passwordHash = BcryptUtil.bcryptHash(password);
|
user.passwordHash = BcryptUtil.bcryptHash(password);
|
||||||
user.createdAt = Instant.now();
|
user.createdAt = Instant.now();
|
||||||
|
user.mustChangePassword = false;
|
||||||
|
user.isAdmin = false;
|
||||||
user.persist();
|
user.persist();
|
||||||
|
|
||||||
return Optional.of(user);
|
return Optional.of(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public User registerInternal(String username, String password, boolean isAdmin, boolean mustChangePassword) {
|
||||||
|
User user = new User();
|
||||||
|
user.id = UUID.randomUUID();
|
||||||
|
user.username = username.toLowerCase();
|
||||||
|
user.passwordHash = BcryptUtil.bcryptHash(password);
|
||||||
|
user.createdAt = Instant.now();
|
||||||
|
user.mustChangePassword = mustChangePassword;
|
||||||
|
user.isAdmin = isAdmin;
|
||||||
|
user.persist();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<User> authenticate(String username, String password) {
|
public Optional<User> authenticate(String username, String password) {
|
||||||
if (username == null || password == null) return Optional.empty();
|
if (username == null || password == null) return Optional.empty();
|
||||||
User user = User.findByUsernameCaseInsensitive(username.trim());
|
User user = User.findByUsernameCaseInsensitive(username.trim());
|
||||||
@@ -50,26 +73,63 @@ public class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String buildToken(UUID userId) {
|
public String buildToken(UUID userId) {
|
||||||
|
User user = User.findById(userId);
|
||||||
|
if (user == null) throw new IllegalStateException("user not found: " + userId);
|
||||||
|
Set<String> groups = new HashSet<>();
|
||||||
|
groups.add("user");
|
||||||
|
if (user.isAdmin) groups.add("admin");
|
||||||
return Jwt.issuer(issuer)
|
return Jwt.issuer(issuer)
|
||||||
.subject(userId.toString())
|
.subject(userId.toString())
|
||||||
.groups(Set.of("user"))
|
.groups(groups)
|
||||||
.expiresIn(Duration.ofSeconds(86400))
|
.expiresIn(Duration.ofSeconds(86400))
|
||||||
.sign();
|
.sign();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ChangePasswordResult changePassword(UUID userId, String currentPassword, String newPassword) {
|
||||||
|
User user = User.findById(userId);
|
||||||
|
if (user == null) return ChangePasswordResult.NOT_FOUND;
|
||||||
|
if (currentPassword == null || !BcryptUtil.matches(currentPassword, user.passwordHash)) {
|
||||||
|
return ChangePasswordResult.WRONG_CURRENT_PASSWORD;
|
||||||
|
}
|
||||||
|
if (newPassword == null || newPassword.length() < MIN_PASSWORD_LENGTH) {
|
||||||
|
return ChangePasswordResult.WEAK_NEW_PASSWORD;
|
||||||
|
}
|
||||||
|
user.passwordHash = BcryptUtil.bcryptHash(newPassword);
|
||||||
|
user.mustChangePassword = false;
|
||||||
|
user.persist();
|
||||||
|
return ChangePasswordResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public boolean resetPasswordAsAdmin(UUID requesterId, String targetUsername, String newPassword) {
|
||||||
|
User requester = User.findById(requesterId);
|
||||||
|
if (requester == null || !requester.isAdmin) return false;
|
||||||
|
if (targetUsername == null || newPassword == null) return false;
|
||||||
|
if (newPassword.length() < MIN_PASSWORD_LENGTH) return false;
|
||||||
|
User target = User.findByUsernameCaseInsensitive(targetUsername.trim());
|
||||||
|
if (target == null) return false;
|
||||||
|
target.passwordHash = BcryptUtil.bcryptHash(newPassword);
|
||||||
|
target.mustChangePassword = true;
|
||||||
|
target.persist();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<AuthMeResponse> getUserFromToken(JsonWebToken jwt) {
|
public Optional<AuthMeResponse> getUserFromToken(JsonWebToken jwt) {
|
||||||
|
return getEntityFromToken(jwt).map(u -> new AuthMeResponse(u.id, u.username, u.createdAt, u.mustChangePassword, u.isAdmin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> getEntityFromToken(JsonWebToken jwt) {
|
||||||
if (jwt == null || jwt.getSubject() == null) return Optional.empty();
|
if (jwt == null || jwt.getSubject() == null) return Optional.empty();
|
||||||
try {
|
try {
|
||||||
UUID userId = UUID.fromString(jwt.getSubject());
|
UUID userId = UUID.fromString(jwt.getSubject());
|
||||||
User user = User.findById(userId);
|
return Optional.ofNullable(User.findById(userId));
|
||||||
if (user == null) return Optional.empty();
|
|
||||||
return Optional.of(new AuthMeResponse(user.id, user.username, user.createdAt));
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AuthMeResponse toAuthMe(User user) {
|
public static AuthMeResponse toAuthMe(User user) {
|
||||||
return new AuthMeResponse(user.id, user.username, user.createdAt);
|
return new AuthMeResponse(user.id, user.username, user.createdAt, user.mustChangePassword, user.isAdmin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.l2.shots.auth;
|
||||||
|
|
||||||
|
import io.quarkus.runtime.StartupEvent;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.enterprise.event.Observes;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un usuario admin por defecto si la base de datos está vacía,
|
||||||
|
* e imprime la contraseña temporal a stdout para que el operador la lea
|
||||||
|
* una sola vez desde el log de Docker.
|
||||||
|
*
|
||||||
|
* Idempotente: si ya existe algún user, no hace nada.
|
||||||
|
* Desactivable con app.bootstrap.admin.enabled=false (env: APP_BOOTSTRAP_ADMIN_ENABLED).
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class BootstrapAdmin {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(BootstrapAdmin.class);
|
||||||
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
|
// Alfabeto sin chars ambiguos (0/o/O, 1/l/I) para no cansar al tipear.
|
||||||
|
private static final char[] ALPHABET =
|
||||||
|
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789abcdefghjkmnpqrstuvwxyz".toCharArray();
|
||||||
|
private static final int PASSWORD_LENGTH = 20;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AuthService authService;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "app.bootstrap.admin.enabled", defaultValue = "true")
|
||||||
|
boolean enabled;
|
||||||
|
|
||||||
|
@ConfigProperty(name = "app.bootstrap.admin.username", defaultValue = "admin")
|
||||||
|
String username;
|
||||||
|
|
||||||
|
void onStart(@Observes StartupEvent ev) {
|
||||||
|
if (!enabled) {
|
||||||
|
LOG.info("Bootstrap admin disabled (app.bootstrap.admin.enabled=false)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long count = User.count();
|
||||||
|
if (count > 0) {
|
||||||
|
LOG.infof("Bootstrap admin skipped: %d users already exist", count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String tempPassword = generatePassword();
|
||||||
|
authService.registerInternal(username, tempPassword, true, true);
|
||||||
|
|
||||||
|
String banner = "\n"
|
||||||
|
+ "BOOTSTRAP-ADMIN-USERNAME " + username + "\n"
|
||||||
|
+ "BOOTSTRAP-ADMIN-PASSWORD " + tempPassword + "\n"
|
||||||
|
+ "BOOTSTRAP-ADMIN-CHANGE This password MUST be changed on first login via POST /api/auth/change-password\n";
|
||||||
|
|
||||||
|
LOG.warn(banner);
|
||||||
|
// Backup a stdout por si algún pipeline colecta stdout pero no el logger JBoss.
|
||||||
|
System.out.print(banner);
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generatePassword() {
|
||||||
|
StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
|
||||||
|
for (int i = 0; i < PASSWORD_LENGTH; i++) {
|
||||||
|
sb.append(ALPHABET[RANDOM.nextInt(ALPHABET.length)]);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.l2.shots.auth;
|
||||||
|
|
||||||
|
public class ChangePasswordRequest {
|
||||||
|
public String currentPassword;
|
||||||
|
public String newPassword;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.l2.shots.auth;
|
||||||
|
|
||||||
|
import jakarta.annotation.Priority;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.Priorities;
|
||||||
|
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||||
|
import jakarta.ws.rs.container.ContainerRequestFilter;
|
||||||
|
import jakarta.ws.rs.core.Context;
|
||||||
|
import jakarta.ws.rs.core.HttpHeaders;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import jakarta.ws.rs.ext.Provider;
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bloquea todo request autenticado para usuarios con mustChangePassword=true,
|
||||||
|
* salvo /change-password y /logout (para que pueda salir si metió mal la clave).
|
||||||
|
*
|
||||||
|
* Corre DESPUÉS de la auth para que el JWT ya esté parseado.
|
||||||
|
*/
|
||||||
|
@Provider
|
||||||
|
@Priority(Priorities.AUTHENTICATION + 100)
|
||||||
|
public class MustChangePasswordFilter implements ContainerRequestFilter {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AuthService authService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
JwtCookieAuth jwtCookieAuth;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
HttpHeaders headers;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(ContainerRequestContext ctx) {
|
||||||
|
Optional<JsonWebToken> jwtOpt;
|
||||||
|
try {
|
||||||
|
jwtOpt = jwtCookieAuth.extractToken(headers);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (jwtOpt.isEmpty() || jwtOpt.get() == null) return;
|
||||||
|
|
||||||
|
Optional<User> userOpt = authService.getEntityFromToken(jwtOpt.get());
|
||||||
|
if (userOpt.isEmpty()) return;
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
if (!user.mustChangePassword) return;
|
||||||
|
|
||||||
|
String path = ctx.getUriInfo().getPath();
|
||||||
|
if (path.startsWith("/")) path = path.substring(1);
|
||||||
|
if (path.startsWith("api/auth/change-password")) return;
|
||||||
|
if (path.startsWith("api/auth/logout")) return;
|
||||||
|
|
||||||
|
ctx.abortWith(Response.status(403)
|
||||||
|
.entity(new MustChangeBody())
|
||||||
|
.type(MediaType.APPLICATION_JSON)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MustChangeBody {
|
||||||
|
public String error = "Debe cambiar la contraseña antes de continuar";
|
||||||
|
public boolean mustChangePassword = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,12 @@ public class User extends PanacheEntityBase {
|
|||||||
@Column(name = "created_at", nullable = false)
|
@Column(name = "created_at", nullable = false)
|
||||||
public Instant createdAt;
|
public Instant createdAt;
|
||||||
|
|
||||||
|
@Column(name = "must_change_password", nullable = false)
|
||||||
|
public boolean mustChangePassword = false;
|
||||||
|
|
||||||
|
@Column(name = "is_admin", nullable = false)
|
||||||
|
public boolean isAdmin = false;
|
||||||
|
|
||||||
public static User findByUsername(String username) {
|
public static User findByUsername(String username) {
|
||||||
return find("username", username.toLowerCase()).firstResult();
|
return find("username", username.toLowerCase()).firstResult();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,8 @@ app.auth.cookie-max-age-seconds=86400
|
|||||||
# Security
|
# Security
|
||||||
quarkus.http.auth.proactive=false
|
quarkus.http.auth.proactive=false
|
||||||
|
|
||||||
|
# Bootstrap admin (default user created on first boot of an empty DB)
|
||||||
|
app.bootstrap.admin.enabled=true
|
||||||
|
app.bootstrap.admin.username=admin
|
||||||
|
|
||||||
%native.quarkus.native.resources.includes=META-INF/resources/.*,publicKey.pem,privateKey.pem
|
%native.quarkus.native.resources.includes=META-INF/resources/.*,publicKey.pem,privateKey.pem
|
||||||
|
|||||||
Reference in New Issue
Block a user