Browse Source

Merge branch 'main' into sqlite

sqlite
Stephan Richter 3 months ago
parent
commit
893f6f418c
  1. 3
      de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
  2. 5
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java
  3. 39
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  4. 31
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Lock.java
  5. 2
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  6. 10
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  7. 9
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java
  8. 36
      de.srsoftware.oidc.datastore.encrypted/src/main/java/de/srsoftware/oidc/datastore/encrypted/EncryptedUserService.java
  9. 29
      de.srsoftware.oidc.datastore.encrypted/src/test/java/EncryptedUserServiceTest.java
  10. 29
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  11. 26
      de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java
  12. 12
      de.srsoftware.oidc.web/src/main/resources/de/login.html
  13. 8
      de.srsoftware.oidc.web/src/main/resources/en/login.html
  14. 11
      de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js
  15. 4
      de.srsoftware.oidc.web/src/main/resources/en/todo.html
  16. 1
      de.srsoftware.utils/build.gradle
  17. 40
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java
  18. 24
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java
  19. 6
      de.srsoftware.utils/src/main/java/de/srsoftware/utils/Result.java

3
de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java

@ -11,6 +11,7 @@ import com.sun.net.httpserver.HttpExchange; @@ -11,6 +11,7 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;
import de.srsoftware.utils.Error;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@ -176,7 +177,7 @@ public abstract class PathHandler implements HttpHandler { @@ -176,7 +177,7 @@ public abstract class PathHandler implements HttpHandler {
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
if (o instanceof List<?> list) o = new JSONArray(list);
if (o instanceof Map<?, ?> map) o = new JSONObject(map);
if (o instanceof JSONObject) ex.getResponseHeaders().add(CONTENT_TYPE, JSON);
if (o instanceof Error<?> error) o = error.json();
return sendContent(ex, status, o.toString().getBytes(UTF_8));
}

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

@ -6,6 +6,7 @@ public class Constants { @@ -6,6 +6,7 @@ public class Constants {
public static final String ACCESS_TOKEN = "access_token";
public static final String APP_NAME = "LightOIDC";
public static final String AT_HASH = "at_hash";
public static final String ATTEMPTS = "attempts";
public static final String AUTH_CODE = "authorization_code";
public static final String AUTHORZED = "authorized";
public static final String BEARER = "Bearer";
@ -20,6 +21,9 @@ public class Constants { @@ -20,6 +21,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";
@ -38,6 +42,7 @@ public class Constants { @@ -38,6 +42,7 @@ public class Constants {
public static final String OPENID = "openid";
public static final String REDIRECT_URI = "redirect_uri";
public static final String REDIRECT_URIS = "redirect_uris";
public static final String RELEASE = "release";
public static final String REQUEST_NOT_SUPPORTED = "request_not_supported";
public static final String RESPONSE_TYPE = "response_type";
public static final String SALT = "salt";

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

@ -1,13 +1,18 @@ @@ -1,13 +1,18 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api;
import static java.util.Optional.empty;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.Lock;
import de.srsoftware.oidc.api.data.User;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import de.srsoftware.utils.Result;
import java.time.Instant;
import java.util.*;
public interface UserService {
Map<String, Lock> failedLogins = new HashMap<>();
/**
* create a new access token for a given user
* @param user
@ -22,13 +27,25 @@ public interface UserService { @@ -22,13 +27,25 @@ 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> load(String username, String password);
public boolean passwordMatches(String plaintextPassword, User user);
public UserService save(User user);
public UserService updatePassword(User user, String plaintextPassword);
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);
public default UserService unlock(String key) {
failedLogins.remove(key);
return this;
}
public UserService updatePassword(User user, String plaintextPassword);
}

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

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.api.data;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class Lock {
private int attempts;
private Instant releaseTime;
public Lock() {
this.attempts = 0;
}
public Lock count() {
attempts++;
if (attempts > 13) attempts = 13;
var seconds = 5;
for (long i = 0; i < attempts; i++) seconds *= 2;
releaseTime = Instant.now().plusSeconds(seconds).truncatedTo(ChronoUnit.SECONDS);
return this;
}
public int attempts() {
return attempts;
}
public Instant releaseTime() {
return releaseTime;
}
}

2
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java

@ -77,7 +77,7 @@ public class Application { @@ -77,7 +77,7 @@ public class Application {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
var staticPages = (StaticPages) new StaticPages(basePath).bindPath(STATIC_PATH, FAVICON).on(server);
new Forward(INDEX).bindPath(ROOT).on(server);
new WellKnownController().bindPath(WELL_KNOWN).on(server);
new WellKnownController().bindPath(WELL_KNOWN, "/realms/oidc" + WELL_KNOWN).on(server);
new UserController(mailConfig, sessionService, userService, staticPages).bindPath(API_USER).on(server);
var tokenControllerConfig = new TokenController.Configuration("https://lightoidc.srsoftware.de", 10); // TODO configure or derive from hostname
new TokenController(authService, clientService, keyManager, userService, tokenControllerConfig).bindPath(API_TOKEN).on(server);

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

@ -15,6 +15,8 @@ import de.srsoftware.oidc.api.*; @@ -15,6 +15,8 @@ import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.Permission;
import de.srsoftware.oidc.api.data.Session;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.Payload;
import de.srsoftware.utils.Result;
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.io.IOException;
@ -193,11 +195,11 @@ public class UserController extends Controller { @@ -193,11 +195,11 @@ public class UserController extends Controller {
var username = body.has(USERNAME) ? body.getString(USERNAME) : 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);
Optional<User> user = users.load(username, password);
if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
Result<User> result = users.login(username, password);
if (result instanceof Payload<User> user) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
return sendContent(ex, HTTP_UNAUTHORIZED, result);
}
private boolean logout(HttpExchange ex, Session session) throws IOException {

9
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/WellKnownController.java

@ -5,6 +5,7 @@ package de.srsoftware.oidc.backend; @@ -5,6 +5,7 @@ package de.srsoftware.oidc.backend;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.http.PathHandler;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class WellKnownController extends PathHandler {
@ -19,6 +20,12 @@ public class WellKnownController extends PathHandler { @@ -19,6 +20,12 @@ public class WellKnownController extends PathHandler {
private boolean openidConfig(HttpExchange ex) throws IOException {
var host = hostname(ex);
return sendContent(ex, Map.of("token_endpoint", host + "/api/token", "authorization_endpoint", host + "/web/authorization.html", "userinfo_endpoint", host + "/api/user/info", "jwks_uri", host + "/api/jwks.json", "issuer", "https://lightoidc.srsoftware.de"));
return sendContent(ex, Map.of("token_endpoint", host + "/api/token", //
"authorization_endpoint", host + "/web/authorization.html", //
"userinfo_endpoint", host + "/api/user/info", //
"jwks_uri", host + "/api/jwks.json", //
"issuer", "https://lightoidc.srsoftware.de", //
"id_token_signing_alg_values_supported", List.of("RS256"), //
"subject_types_supported", List.of("public", "pairwise")));
}
}

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

@ -1,20 +1,23 @@ @@ -1,20 +1,23 @@
/* © 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.UserService;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.Error;
import de.srsoftware.utils.PasswordHasher;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import de.srsoftware.utils.Payload;
import de.srsoftware.utils.Result;
import java.util.*;
public class EncryptedUserService extends EncryptedConfig implements UserService {
private final UserService backend;
private final PasswordHasher hasher;
private static final System.Logger LOG = System.getLogger(EncryptedUserService.class.getSimpleName());
private final UserService backend;
private final PasswordHasher hasher;
public EncryptedUserService(UserService backend, String key, String salt, PasswordHasher passHasher) {
super(key, salt);
@ -94,13 +97,26 @@ public class EncryptedUserService extends EncryptedConfig implements UserService @@ -94,13 +97,26 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
}
@Override
public Optional<User> load(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 Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
}
for (var encryptedUser : backend.list()) {
var decryptedUser = decrypt(encryptedUser);
if (username.equals(decryptedUser.username()) && hasher.matches(password, decryptedUser.hashedPassword())) return Optional.of(decryptedUser);
if (!username.equals(decryptedUser.username())) continue;
if (hasher.matches(password, decryptedUser.hashedPassword())) {
this.unlock(username);
return Payload.of(decryptedUser);
}
}
return empty();
var lock = lock(username);
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
}
@Override

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

@ -1,13 +1,17 @@ @@ -1,13 +1,17 @@
/* © SRSoftware 2024 */
import static de.srsoftware.oidc.api.Constants.*;
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.UserService;
import de.srsoftware.oidc.api.UserServiceTest;
import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
import de.srsoftware.utils.Error;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.Payload;
import de.srsoftware.utils.Result;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
@ -15,6 +19,7 @@ import org.junit.jupiter.api.AfterEach; @@ -15,6 +19,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class EncryptedUserServiceTest extends UserServiceTest {
private static final System.Logger LOG = System.getLogger(EncryptedUserServiceTest.class.getSimpleName());
private class InMemoryUserService implements UserService {
private final PasswordHasher<String> hasher;
private HashMap<String, User> users = new HashMap<>();
@ -66,8 +71,24 @@ public class EncryptedUserServiceTest extends UserServiceTest { @@ -66,8 +71,24 @@ public class EncryptedUserServiceTest extends UserServiceTest {
}
@Override
public Optional<User> load(String username, String password) {
return users.values().stream().filter(user -> user.username().equals(username) && passwordMatches(password, user)).findAny();
public Result<User> login(String username, String password) {
var optLock = getLock(username);
if (optLock.isPresent()) {
var lock = optLock.get();
LOG.log(WARNING, "{} is locked after {} failed logins. Lock will be released at {}", username, lock.attempts(), lock.releaseTime());
return Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
}
for (var entry : users.entrySet()) {
var user = entry.getValue();
if (user.username().equals(username) && passwordMatches(password, user)) {
unlock(username);
return Payload.of(user);
}
}
var lock = lock(username);
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
}
@Override

29
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

@ -10,7 +10,10 @@ import static java.util.Optional.empty; @@ -10,7 +10,10 @@ import static java.util.Optional.empty;
import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.*;
import de.srsoftware.utils.Error;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.Payload;
import de.srsoftware.utils.Result;
import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication;
import java.io.File;
@ -140,6 +143,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -140,6 +143,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return this;
}
@Override
public Set<User> find(String key) {
if (!json.has(USERS)) return Set.of();
@ -175,20 +179,32 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -175,20 +179,32 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
@Override
public Optional<User> load(String user, String password) {
if (!json.has(USERS)) return empty();
public Result<User> login(String username, String password) {
if (!json.has(USERS)) return Error.message(ERROR_LOGIN_FAILED);
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 Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
}
try {
var users = json.getJSONObject(USERS);
for (String userId : users.keySet()) {
var userData = users.getJSONObject(userId);
if (KEYS.stream().map(userData::getString).noneMatch(val -> val.equals(user))) continue;
if (KEYS.stream().map(userData::getString).noneMatch(val -> val.equals(username))) continue;
var loadedUser = User.of(userData, userId).filter(u -> passwordMatches(password, u));
if (loadedUser.isPresent()) return loadedUser;
if (loadedUser.isPresent()) {
unlock(username);
return Payload.of(loadedUser.get());
}
}
return empty();
var lock = lock(username);
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
} catch (Exception e) {
return empty();
return Error.message(ERROR_LOGIN_FAILED);
}
}
@ -210,6 +226,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -210,6 +226,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return save();
}
@Override
public FileStore updatePassword(User user, String plaintextPassword) {
return save(user.hashedPassword(passwordHasher.hash(plaintextPassword, uuid())));

26
de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteUserService.java

@ -1,15 +1,20 @@ @@ -1,15 +1,20 @@
/* © SRSoftware 2024 */
package de.srsoftware.oidc.datastore.sqlite;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.utils.Optionals.nullable;
import static de.srsoftware.utils.Strings.uuid;
import static java.lang.System.Logger.Level.WARNING;
import static java.util.Optional.empty;
import de.srsoftware.oidc.api.UserService;
import de.srsoftware.oidc.api.data.AccessToken;
import de.srsoftware.oidc.api.data.Permission;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.utils.Error;
import de.srsoftware.utils.PasswordHasher;
import de.srsoftware.utils.Payload;
import de.srsoftware.utils.Result;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -209,12 +214,23 @@ public class SqliteUserService extends SqliteStore implements UserService { @@ -209,12 +214,23 @@ public class SqliteUserService extends SqliteStore implements UserService {
}
@Override
public Optional<User> load(String username, String password) {
var candidates = find(username);
for (var user : candidates) {
if (passwordMatches(password, user)) return Optional.of(user);
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 Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
}
return empty();
for (var user : find(username)) {
if (passwordMatches(password, user)) {
this.unlock(username);
return Payload.of(user);
}
}
var lock = lock(username);
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
}
@Override

12
de.srsoftware.oidc.web/src/main/resources/de/login.html

@ -24,10 +24,16 @@ @@ -24,10 +24,16 @@
<th>Passwort</th>
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Passwort *"/></td>
</tr>
<tr id="error" style="display: none">
<tr id="error_login_failed" class="warn" style="display: none">
<th>Fehler</th>
<td class="warning">Anmeldung fehlgeschlagen!</td>
</tr>
<tr id="error_locked" class="warn" style="display: none">
<th>Fehler</th>
<td class="warning">
Account gesperrt bis <span id="release"></span>
</td>
</tr>
<tr>
<td colspan="2">
<label>
@ -47,11 +53,11 @@ @@ -47,11 +53,11 @@
</table>
</fieldset>
<div id="sent" class="warning" style="display: none">
Ein Link zum Zurücksetzen ihres Passworts wurde ihnen per Mail gesendet.
Ein Link zum Zurücksetzen Ihres Passworts wurde ihnen per Mail gesendet.
</div>
</div>
<div id="ad">
Dieser minimale Login-Service wird durch <b>SRSoftware</b> zur Verfügung gestellt. <a href="https://git.srsoftware.de/StephanRichter/LightOidc">Mehr erfahren…</a>
Dieser leichtgewichtige Login-Service wird durch <b>SRSoftware</b> zur Verfügung gestellt. <a href="https://git.srsoftware.de/StephanRichter/LightOidc">Mehr erfahren…</a>
</div>
</body>
</html>

8
de.srsoftware.oidc.web/src/main/resources/en/login.html

@ -24,10 +24,16 @@ @@ -24,10 +24,16 @@
<th>Password</th>
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Password *"/></td>
</tr>
<tr id="error" style="display: none">
<tr id="error_login_failed" class="warn" style="display: none">
<th>Error</th>
<td class="warning">Failed to log in!</td>
</tr>
<tr id="error_locked" class="warn" style="display: none">
<th>Error</th>
<td class="warning">
Your account is locked until <span id="release"></span>
</td>
</tr>
<tr>
<td colspan="2">
<label>

11
de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js

@ -5,6 +5,7 @@ function doRedirect(){ @@ -5,6 +5,7 @@ function doRedirect(){
}
function handleLogin(response){
hideAll('warn');
if (response.ok){
response.headers.forEach(function(val, key) {
console.log('header: '+key+' → '+val);
@ -15,12 +16,12 @@ function handleLogin(response){ @@ -15,12 +16,12 @@ function handleLogin(response){
document.cookie = val;
}
});
response.json().then(body => {
hide('error');
setTimeout(doRedirect,100);
});
response.json().then(body => setTimeout(doRedirect,100));
} else {
show('error');
response.json().then(json => {
if (json.metadata.release) get('release').innerHTML = new Date(json.metadata.release).toLocaleString();
show(json.error);
});
}
}

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

@ -13,8 +13,8 @@ @@ -13,8 +13,8 @@
<div id="content">
<h1>to do…</h1>
<ul>
<li>implement token refresh</li>
<li>Configuration im Frontend</li>
<li>implement token refresh</li>
<li>Configuration im Frontend</li>
</ul>
</div>
</body>

1
de.srsoftware.utils/build.gradle

@ -12,6 +12,7 @@ repositories { @@ -12,6 +12,7 @@ repositories {
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation 'org.json:json:20240303'
}
test {

40
de.srsoftware.utils/src/main/java/de/srsoftware/utils/Error.java

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/* © SRSoftware 2024 */
package de.srsoftware.utils;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
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 cause, Object... tokens) {
var err = new Error<T>(cause);
err.metadata = new HashMap<>();
for (int i = 0; i < tokens.length - 1; i += 2) {
err.metadata.put(tokens[i].toString(), tokens[i + 1]);
}
return err;
}
public JSONObject json() {
var json = new JSONObject(Map.of("error", cause));
if (metadata != null) json.put("metadata", metadata);
return json;
}
}

24
de.srsoftware.utils/src/main/java/de/srsoftware/utils/Payload.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
/* © SRSoftware 2024 */
package de.srsoftware.utils;
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.utils/src/main/java/de/srsoftware/utils/Result.java

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