improved error message display on login papge
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -11,6 +11,7 @@ import com.sun.net.httpserver.HttpExchange;
|
|||||||
import com.sun.net.httpserver.HttpHandler;
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import com.sun.net.httpserver.HttpsExchange;
|
import com.sun.net.httpserver.HttpsExchange;
|
||||||
|
import de.srsoftware.utils.Error;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -176,7 +177,7 @@ public abstract class PathHandler implements HttpHandler {
|
|||||||
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
|
public static boolean sendContent(HttpExchange ex, int status, Object o) throws IOException {
|
||||||
if (o instanceof List<?> list) o = new JSONArray(list);
|
if (o instanceof List<?> list) o = new JSONArray(list);
|
||||||
if (o instanceof Map<?, ?> map) o = new JSONObject(map);
|
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));
|
return sendContent(ex, status, o.toString().getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public class Constants {
|
|||||||
public static final String ACCESS_TOKEN = "access_token";
|
public static final String ACCESS_TOKEN = "access_token";
|
||||||
public static final String APP_NAME = "LightOIDC";
|
public static final String APP_NAME = "LightOIDC";
|
||||||
public static final String AT_HASH = "at_hash";
|
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 AUTH_CODE = "authorization_code";
|
||||||
public static final String AUTHORZED = "authorized";
|
public static final String AUTHORZED = "authorized";
|
||||||
public static final String BEARER = "Bearer";
|
public static final String BEARER = "Bearer";
|
||||||
@@ -41,6 +42,7 @@ public class Constants {
|
|||||||
public static final String OPENID = "openid";
|
public static final String OPENID = "openid";
|
||||||
public static final String REDIRECT_URI = "redirect_uri";
|
public static final String REDIRECT_URI = "redirect_uri";
|
||||||
public static final String REDIRECT_URIS = "redirect_uris";
|
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 REQUEST_NOT_SUPPORTED = "request_not_supported";
|
||||||
public static final String RESPONSE_TYPE = "response_type";
|
public static final String RESPONSE_TYPE = "response_type";
|
||||||
public static final String SALT = "salt";
|
public static final String SALT = "salt";
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
/* © 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import static java.util.Optional.empty;
|
|||||||
import de.srsoftware.oidc.api.data.AccessToken;
|
import de.srsoftware.oidc.api.data.AccessToken;
|
||||||
import de.srsoftware.oidc.api.data.Lock;
|
import de.srsoftware.oidc.api.data.Lock;
|
||||||
import de.srsoftware.oidc.api.data.User;
|
import de.srsoftware.oidc.api.data.User;
|
||||||
|
import de.srsoftware.utils.Result;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package de.srsoftware.oidc.api.data;
|
package de.srsoftware.oidc.api.data;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
public class Lock {
|
public class Lock {
|
||||||
private int attempts;
|
private int attempts;
|
||||||
@@ -16,7 +17,7 @@ public class Lock {
|
|||||||
if (attempts > 13) attempts = 13;
|
if (attempts > 13) attempts = 13;
|
||||||
var seconds = 5;
|
var seconds = 5;
|
||||||
for (long i = 0; i < attempts; i++) seconds *= 2;
|
for (long i = 0; i < attempts; i++) seconds *= 2;
|
||||||
releaseTime = Instant.now().plusSeconds(seconds);
|
releaseTime = Instant.now().plusSeconds(seconds).truncatedTo(ChronoUnit.SECONDS);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import de.srsoftware.oidc.api.*;
|
|||||||
import de.srsoftware.oidc.api.data.Permission;
|
import de.srsoftware.oidc.api.data.Permission;
|
||||||
import de.srsoftware.oidc.api.data.Session;
|
import de.srsoftware.oidc.api.data.Session;
|
||||||
import de.srsoftware.oidc.api.data.User;
|
import de.srsoftware.oidc.api.data.User;
|
||||||
|
import de.srsoftware.utils.Payload;
|
||||||
|
import de.srsoftware.utils.Result;
|
||||||
import jakarta.mail.*;
|
import jakarta.mail.*;
|
||||||
import jakarta.mail.internet.*;
|
import jakarta.mail.internet.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -193,11 +195,11 @@ public class UserController extends Controller {
|
|||||||
|
|
||||||
var username = body.has(USERNAME) ? body.getString(USERNAME) : null;
|
var username = body.has(USERNAME) ? body.getString(USERNAME) : null;
|
||||||
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);
|
||||||
|
|
||||||
Optional<User> user = usersepa.login(username, password);
|
Result<User> result = users.login(username, password);
|
||||||
if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
|
if (result instanceof Payload<User> user) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get());
|
||||||
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
|
return sendContent(ex, HTTP_UNAUTHORIZED, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean logout(HttpExchange ex, Session session) throws IOException {
|
private boolean logout(HttpExchange ex, Session session) throws IOException {
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ 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;
|
||||||
|
import de.srsoftware.utils.Error;
|
||||||
import de.srsoftware.utils.PasswordHasher;
|
import de.srsoftware.utils.PasswordHasher;
|
||||||
|
import de.srsoftware.utils.Payload;
|
||||||
|
import de.srsoftware.utils.Result;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class EncryptedUserService extends EncryptedConfig implements UserService {
|
public class EncryptedUserService extends EncryptedConfig implements UserService {
|
||||||
@@ -103,8 +103,7 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
|||||||
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());
|
||||||
Error<User> err = Error.message(ERROR_LOCKED);
|
return Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
|
||||||
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);
|
||||||
@@ -117,8 +116,7 @@ public class EncryptedUserService extends EncryptedConfig implements UserService
|
|||||||
|
|
||||||
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());
|
||||||
Error<User> err = Error.message(ERROR_LOGIN_FAILED);
|
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
|
||||||
return err.metadata("release", lock.releaseTime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
|
import static de.srsoftware.oidc.api.Constants.*;
|
||||||
import static de.srsoftware.utils.Optionals.nullable;
|
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.*;
|
||||||
import de.srsoftware.oidc.api.UserService;
|
|
||||||
import de.srsoftware.oidc.api.UserServiceTest;
|
|
||||||
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;
|
||||||
import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
|
import de.srsoftware.oidc.datastore.encrypted.EncryptedUserService;
|
||||||
|
import de.srsoftware.utils.Error;
|
||||||
import de.srsoftware.utils.PasswordHasher;
|
import de.srsoftware.utils.PasswordHasher;
|
||||||
|
import de.srsoftware.utils.Payload;
|
||||||
|
import de.srsoftware.utils.Result;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -74,18 +76,19 @@ public class EncryptedUserServiceTest extends UserServiceTest {
|
|||||||
if (optLock.isPresent()) {
|
if (optLock.isPresent()) {
|
||||||
var lock = optLock.get();
|
var lock = optLock.get();
|
||||||
LOG.log(WARNING, "{} is locked after {} failed logins. Lock will be released at {}", username, lock.attempts(), lock.releaseTime());
|
LOG.log(WARNING, "{} is locked after {} failed logins. Lock will be released at {}", username, lock.attempts(), lock.releaseTime());
|
||||||
return Optional.empty();
|
return Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var entry : users.entrySet()) {
|
for (var entry : users.entrySet()) {
|
||||||
var user = entry.getValue();
|
var user = entry.getValue();
|
||||||
if (user.username().equals(username) && passwordMatches(password, user)) {
|
if (user.username().equals(username) && passwordMatches(password, user)) {
|
||||||
unlock(username);
|
unlock(username);
|
||||||
return Optional.of(user);
|
return Payload.of(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lock(username);
|
var lock = lock(username);
|
||||||
return Optional.empty();
|
LOG.log(WARNING, "Login failed for {0} → locking account until {1}", username, lock.releaseTime());
|
||||||
|
return Error.message(ERROR_LOGIN_FAILED, RELEASE, lock.releaseTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import static java.util.Optional.empty;
|
|||||||
|
|
||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
import de.srsoftware.oidc.api.data.*;
|
import de.srsoftware.oidc.api.data.*;
|
||||||
|
import de.srsoftware.utils.Error;
|
||||||
import de.srsoftware.utils.PasswordHasher;
|
import de.srsoftware.utils.PasswordHasher;
|
||||||
|
import de.srsoftware.utils.Payload;
|
||||||
|
import de.srsoftware.utils.Result;
|
||||||
import jakarta.mail.Authenticator;
|
import jakarta.mail.Authenticator;
|
||||||
import jakarta.mail.PasswordAuthentication;
|
import jakarta.mail.PasswordAuthentication;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -176,31 +179,32 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<User> login(String user, String password) {
|
public Result<User> login(String username, String password) {
|
||||||
if (!json.has(USERS)) return empty();
|
if (!json.has(USERS)) return Error.message(ERROR_LOGIN_FAILED);
|
||||||
var optLock = getLock(user);
|
if (username == null || username.isBlank()) return Error.message(ERROR_NO_USERNAME);
|
||||||
|
var optLock = getLock(username);
|
||||||
if (optLock.isPresent()) {
|
if (optLock.isPresent()) {
|
||||||
var lock = optLock.get();
|
var lock = optLock.get();
|
||||||
LOG.log(WARNING, "{} is locked after {} failed logins. Lock will be released at {}", user, 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();
|
return Error.message(ERROR_LOCKED, ATTEMPTS, lock.attempts(), RELEASE, lock.releaseTime());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var users = json.getJSONObject(USERS);
|
var users = json.getJSONObject(USERS);
|
||||||
for (String userId : users.keySet()) {
|
for (String userId : users.keySet()) {
|
||||||
var userData = users.getJSONObject(userId);
|
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));
|
var loadedUser = User.of(userData, userId).filter(u -> passwordMatches(password, u));
|
||||||
if (loadedUser.isPresent()) {
|
if (loadedUser.isPresent()) {
|
||||||
unlock(user);
|
unlock(username);
|
||||||
return loadedUser;
|
return Payload.of(loadedUser.get());
|
||||||
}
|
}
|
||||||
lock(userId);
|
|
||||||
}
|
}
|
||||||
lock(user);
|
var lock = lock(username);
|
||||||
return empty();
|
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) {
|
} catch (Exception e) {
|
||||||
return empty();
|
return Error.message(ERROR_LOGIN_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,16 @@
|
|||||||
<th>Passwort</th>
|
<th>Passwort</th>
|
||||||
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Passwort *"/></td>
|
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Passwort *"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="error" style="display: none">
|
<tr id="error_login_failed" class="warn" style="display: none">
|
||||||
<th>Fehler</th>
|
<th>Fehler</th>
|
||||||
<td class="warning">Anmeldung fehlgeschlagen!</td>
|
<td class="warning">Anmeldung fehlgeschlagen!</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<label>
|
<label>
|
||||||
@@ -47,11 +53,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div id="sent" class="warning" style="display: none">
|
<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>
|
</div>
|
||||||
<div id="ad">
|
<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>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,10 +24,16 @@
|
|||||||
<th>Password</th>
|
<th>Password</th>
|
||||||
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Password *"/></td>
|
<td><input type="password" id="password" onkeydown="keyDown()" placeholder="Password *"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="error" style="display: none">
|
<tr id="error_login_failed" class="warn" style="display: none">
|
||||||
<th>Error</th>
|
<th>Error</th>
|
||||||
<td class="warning">Failed to log in!</td>
|
<td class="warning">Failed to log in!</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<label>
|
<label>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ function doRedirect(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin(response){
|
function handleLogin(response){
|
||||||
|
hideAll('warn');
|
||||||
if (response.ok){
|
if (response.ok){
|
||||||
response.headers.forEach(function(val, key) {
|
response.headers.forEach(function(val, key) {
|
||||||
console.log('header: '+key+' → '+val);
|
console.log('header: '+key+' → '+val);
|
||||||
@@ -15,12 +16,12 @@ function handleLogin(response){
|
|||||||
document.cookie = val;
|
document.cookie = val;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
response.json().then(body => {
|
response.json().then(body => setTimeout(doRedirect,100));
|
||||||
hide('error');
|
|
||||||
setTimeout(doRedirect,100);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
show('error');
|
response.json().then(json => {
|
||||||
|
if (json.metadata.release) get('release').innerHTML = new Date(json.metadata.release).toLocaleString();
|
||||||
|
show(json.error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
testImplementation platform('org.junit:junit-bom:5.10.0')
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
|
implementation 'org.json:json:20240303'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.api;
|
package de.srsoftware.utils;
|
||||||
|
|
||||||
|
|
||||||
public class Payload<T> implements Result<T> {
|
public class Payload<T> implements Result<T> {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.api;
|
package de.srsoftware.utils;
|
||||||
|
|
||||||
public interface Result<T> {
|
public interface Result<T> {
|
||||||
public boolean isError();
|
public boolean isError();
|
||||||
Reference in New Issue
Block a user