Browse Source

preparing to pass error messages to client

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
devel
Stephan Richter 1 month ago
parent
commit
951c65c121
  1. 3
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java
  2. 36
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Error.java
  3. 24
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Payload.java
  4. 6
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Result.java
  5. 16
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  6. 4
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Lock.java
  7. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  8. 18
      de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
  9. 3
      de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java
  10. 1
      de.srsoftware.oidc.web/src/main/resources/en/todo.html

3
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java

@ -20,6 +20,9 @@ public class Constants { @@ -20,6 +20,9 @@ public class Constants {
public static final String DAYS = "days";
public static final String ENCRYPTION_KEY = "encryption_key";
public static final String ERROR_DESCRIPTION = "error_description";
public static final String ERROR_LOCKED = "error_locked";
public static final String ERROR_LOGIN_FAILED = "error_login_failed";
public static final String ERROR_NO_USERNAME = "error_no_username";
public static final String EXPIRATION = "expiration";
public static final String EXPIRES_IN = "expires_in";
public static final String GRANT_TYPE = "grant_type";

36
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Error.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import java.util.HashMap;
import java.util.Map;
public class Error<T> implements Result<T> {
private final String cause;
private Map<String, Object> metadata;
public Error(String cause) {
this.cause = cause;
}
public String cause() {
return cause;
}
@Override
public boolean isError() {
return true;
}
public static <T> Error<T> message(String text) {
return new Error<T>(text);
}
public Error<T> metadata(Object... tokens) {
metadata = new HashMap<String, Object>();
for (int i = 0; i < tokens.length - 1; i += 2) {
metadata.put(tokens[i].toString(), tokens[i + 1]);
}
return this;
}
}

24
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Payload.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
public class Payload<T> implements Result<T> {
private final T object;
public Payload(T object) {
this.object = object;
}
public static <T> Payload<T> of(T object) {
return new Payload<>(object);
}
@Override
public boolean isError() {
return false;
}
public T get() {
return object;
}
}

6
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Result.java

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
public interface Result<T> {
public boolean isError();
}

16
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java

@ -26,19 +26,19 @@ public interface UserService { @@ -26,19 +26,19 @@ public interface UserService {
* @param accessToken
* @return
*/
public Optional<User> forToken(String accessToken);
public UserService init(User defaultUser);
public List<User> list();
public Set<User> find(String idOrEmail);
public Optional<User> forToken(String accessToken);
public UserService init(User defaultUser);
public List<User> list();
public Set<User> find(String idOrEmail);
public default Optional<Lock> getLock(String key) {
var failedLogin = failedLogins.get(key);
if (failedLogin == null || failedLogin.releaseTime().isBefore(Instant.now())) return empty();
return Optional.of(failedLogin);
}
public Optional<User> load(String id);
public Optional<User> login(String username, String password);
public default Lock lock(String key) {
return failedLogins.computeIfAbsent(key,k -> new Lock()).count();
public Optional<User> load(String id);
public Result<User> login(String username, String password);
public default Lock lock(String key) {
return failedLogins.computeIfAbsent(key, k -> new Lock()).count();
}
public boolean passwordMatches(String plaintextPassword, User user);
public UserService save(User user);

4
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Lock.java

@ -4,8 +4,8 @@ package de.srsoftware.oidc.api.data; @@ -4,8 +4,8 @@ package de.srsoftware.oidc.api.data;
import java.time.Instant;
public class Lock {
private int attempts;
private Instant releaseTime;
private int attempts;
private Instant releaseTime;
public Lock() {
this.attempts = 0;

2
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java

@ -195,7 +195,7 @@ public class UserController extends Controller { @@ -195,7 +195,7 @@ public class UserController extends Controller {
var password = body.has(PASSWORD) ? body.getString(PASSWORD) : null;
var trust = body.has(TRUST) ? body.getBoolean(TRUST) : false;
Optional<User> user = users.login(username, password);
Optional<User> user = usersepa.login(username, password);
if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
}

18
de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java

@ -1,9 +1,13 @@ @@ -1,9 +1,13 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.encrypted;
import static de.srsoftware.oidc.api.Constants.*;
import static java.lang.System.Logger.Level.WARNING;
import static java.util.Optional.empty;
import de.srsoftware.oidc.api.Error;
import de.srsoftware.oidc.api.Payload;
import de.srsoftware.oidc.api.Result;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User;
@ -93,26 +97,28 @@ public class EncryptedUserService extends EncryptedConfig implements UserService @@ -93,26 +97,28 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
}
@Override
public Optional<User> login(String username, String password) {
if (username == null || username.isBlank()) return empty();
public Result<User> login(String username, String password) {
if (username == null || username.isBlank()) return Error.message(ERROR_NO_USERNAME);
var optLock = getLock(username);
if (optLock.isPresent()) {
var lock = optLock.get();
LOG.log(WARNING, "{0} is locked after {1} failed logins. Lock will be released at {2}", username, lock.attempts(), lock.releaseTime());
return empty();
Error<User> err = Error.message(ERROR_LOCKED);
return err.metadata("attempts", lock.attempts(), "release", lock.releaseTime());
}
for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (!username.equals(decryptedUser.username())) continue;
if (hasher.matches(password, decryptedUser.hashedPassword())) {
this.unlock(username);
return Optional.of(decryptedUser);
return Payload.of(decryptedUser);
}
}
var lock = lock(username);
LOG.log(WARNING,"Login failed for {0} → locking account until {1}",username,lock.releaseTime());
return empty();
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
Error<User> err = Error.message(ERROR_LOGIN_FAILED);
return err.metadata("release", lock.releaseTime());
}
@Override

3
de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java

@ -3,6 +3,7 @@ import static de.srsoftware.utils.Optionals.nullable; @@ -3,6 +3,7 @@ import static de.srsoftware.utils.Optionals.nullable;
import static de.srsoftware.utils.Strings.uuid;
import static java.lang.System.Logger.Level.WARNING;
import de.srsoftware.oidc.api.Result;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.UserServiceTest;
import de.srsoftware.oidc.api.data.AccessToken;
@ -68,7 +69,7 @@ public class EncryptedUserServiceTest extends UserServiceTest { @@ -68,7 +69,7 @@ public class EncryptedUserServiceTest extends UserServiceTest {
}
@Override
public Optional<User> login(String username, String password) {
public Result<User> login(String username, String password) {
var optLock = getLock(username);
if (optLock.isPresent()) {
var lock = optLock.get();

1
de.srsoftware.oidc.web/src/main/resources/en/todo.html

@ -13,7 +13,6 @@ @@ -13,7 +13,6 @@
<div id="content">
<h1>to do…</h1>
<ul>
<li>implement brute-force countermeasures</li>
<li>implement token refresh</li>
<li>Configuration im Frontend</li>
</ul>

Loading…
Cancel
Save