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. 4
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  6. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  7. 18
      de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
  8. 3
      de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java
  9. 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 {
public static final String DAYS = "days"; public static final String DAYS = "days";
public static final String ENCRYPTION_KEY = "encryption_key"; public static final String ENCRYPTION_KEY = "encryption_key";
public static final String ERROR_DESCRIPTION = "error_description"; 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 EXPIRATION = "expiration";
public static final String EXPIRES_IN = "expires_in"; public static final String EXPIRES_IN = "expires_in";
public static final String GRANT_TYPE = "grant_type"; 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 @@
/* © 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 @@
/* © 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 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
public interface Result<T> {
public boolean isError();
}

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

@ -36,9 +36,9 @@ public interface UserService {
return Optional.of(failedLogin); return Optional.of(failedLogin);
} }
public Optional<User> load(String id); public Optional<User> load(String id);
public Optional<User> login(String username, String password); public Result<User> login(String username, String password);
public default Lock lock(String key) { public default Lock lock(String key) {
return failedLogins.computeIfAbsent(key,k -> new Lock()).count(); return failedLogins.computeIfAbsent(key, k -> new Lock()).count();
} }
public boolean passwordMatches(String plaintextPassword, User user); public boolean passwordMatches(String plaintextPassword, User user);
public UserService save(User user); public UserService save(User user);

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

@ -195,7 +195,7 @@ public class UserController extends Controller {
var password = body.has(PASSWORD) ? body.getString(PASSWORD) : null; var password = body.has(PASSWORD) ? body.getString(PASSWORD) : null;
var trust = body.has(TRUST) ? body.getBoolean(TRUST) : false; 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()); if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex); 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 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.encrypted; package de.srsoftware.oidc.datastore.encrypted;
import static de.srsoftware.oidc.api.Constants.*;
import static java.lang.System.Logger.Level.WARNING; import static java.lang.System.Logger.Level.WARNING;
import static java.util.Optional.empty; 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.UserService;
import de.srsoftware.oidc.api.data.AccessToken; import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
@ -93,26 +97,28 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
} }
@Override @Override
public Optional<User> login(String username, String password) { public Result<User> login(String username, String password) {
if (username == null || username.isBlank()) return empty(); if (username == null || username.isBlank()) return Error.message(ERROR_NO_USERNAME);
var optLock = getLock(username); var optLock = getLock(username);
if (optLock.isPresent()) { if (optLock.isPresent()) {
var lock = optLock.get(); 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()); 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()) { for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser); var decryptedUser = decrypt(encryptedUser);
if (!username.equals(decryptedUser.username())) continue; if (!username.equals(decryptedUser.username())) continue;
if (hasher.matches(password, decryptedUser.hashedPassword())) { if (hasher.matches(password, decryptedUser.hashedPassword())) {
this.unlock(username); this.unlock(username);
return Optional.of(decryptedUser); return Payload.of(decryptedUser);
} }
} }
var lock = lock(username); var lock = lock(username);
LOG.log(WARNING,"Login failed for {0} → locking account until {1}",username,lock.releaseTime()); LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
return empty(); Error<User> err = Error.message(ERROR_LOGIN_FAILED);
return err.metadata("release", lock.releaseTime());
} }
@Override @Override

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

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

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

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

Loading…
Cancel
Save