diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java index 2218114..34b651d 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java @@ -37,6 +37,7 @@ public class Constants { public static final String RESPONSE_TYPE = "response_type"; public static final String SCOPE = "scope"; public static final String SECRET = "secret"; + public static final String SESSION_DURATION = "session_duration"; public static final String SMTP_USER = "smtp_user"; public static final String SMTP_PASSWORD = "smtp_pass"; public static final String SMTP_AUTH = "smtp_auth"; diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java index 809574b..86adcf6 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java @@ -13,8 +13,9 @@ public interface UserService { * @param user * @return */ - public AccessToken accessToken(User user); - public UserService delete(User user); + public AccessToken accessToken(User user); + public Optional consumeToken(String accessToken); + public UserService delete(User user); /** * return the user identified by its access token diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java index 4ac42c3..d4a2aff 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java @@ -1,6 +1,9 @@ /* © SRSoftware 2024 */ package de.srsoftware.oidc.api.data; +import static de.srsoftware.oidc.api.Constants.SESSION_DURATION; + +import java.time.Duration; import java.util.*; import org.json.JSONObject; @@ -14,7 +17,8 @@ public final class User { private final Set permissions = new HashSet<>(); - private String email, hashedPassword, realName, uuid, username; + private String email, hashedPassword, realName, uuid, username; + private Duration sessionDuration = Duration.ofMinutes(10); public User(String username, String hashedPassword, String realName, String email, String uuid) { this.username = username; @@ -66,7 +70,9 @@ public final class User { public Map map(boolean includePassword) { - return includePassword ? Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid, PASSWORD, hashedPassword) : Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid); + return includePassword // + ? Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid, SESSION_DURATION, sessionDuration.toMinutes(), PASSWORD, hashedPassword) + : Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid, SESSION_DURATION, sessionDuration.toMinutes()); } public static Optional of(JSONObject json, String userId) { @@ -81,6 +87,7 @@ public final class User { e.printStackTrace(); } } + if (json.has(SESSION_DURATION)) user.sessionDuration(Duration.ofMinutes(json.getInt(SESSION_DURATION))); return Optional.of(user); } @@ -115,4 +122,12 @@ public final class User { public String uuid() { return uuid; } + + public void sessionDuration(Duration newVal) { + sessionDuration = newVal; + } + + public Duration sessionDuration() { + return sessionDuration; + } } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java index ac58d21..b0ca594 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/TokenController.java @@ -111,7 +111,7 @@ public class TokenController extends PathHandler { String jwToken = createJWT(client, user.get()); ex.getResponseHeaders().add("Cache-Control", "no-store"); JSONObject response = new JSONObject(); - response.put(ACCESS_TOKEN, users.accessToken(user.get())); + response.put(ACCESS_TOKEN, users.accessToken(user.get()).id()); response.put(TOKEN_TYPE, BEARER); response.put(EXPIRES_IN, 3600); response.put(ID_TOKEN, jwToken); diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java index 65a20e2..07b7eb7 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java @@ -17,6 +17,7 @@ import de.srsoftware.oidc.api.data.User; import jakarta.mail.*; import jakarta.mail.internet.*; import java.io.IOException; +import java.time.Duration; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -130,7 +131,7 @@ public class UserController extends Controller { } if (!strong(newPass1)) return sendContent(ex, HTTP_BAD_REQUEST, "weak password"); var token = data.getString(TOKEN); - var optUser = users.forToken(token); + var optUser = users.consumeToken(token); if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token"); var user = optUser.get(); users.updatePassword(user, newPass1); @@ -256,6 +257,7 @@ public class UserController extends Controller { user.username(json.getString(USERNAME)); user.email(json.getString(EMAIL)); user.realName(json.getString(REALNAME)); + user.sessionDuration(Duration.ofMinutes(json.getInt(SESSION_DURATION))); users.save(user); return sendContent(ex, user.map(false)); } diff --git a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java index 8ac3404..f97059e 100644 --- a/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java +++ b/de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java @@ -21,8 +21,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; - import org.json.JSONObject; public class FileStore implements AuthorizationService, ClientService, SessionService, UserService, MailConfig { @@ -38,10 +36,9 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe private final Path storageFile; private final JSONObject json; private final PasswordHasher passwordHasher; - private Duration sessionDuration = Duration.of(10, ChronoUnit.MINUTES); - private Map clients = new HashMap<>(); - private Map accessTokens = new HashMap<>(); - private Map authCodes = new HashMap<>(); + private Map clients = new HashMap<>(); + private Map accessTokens = new HashMap<>(); + private Map authCodes = new HashMap<>(); private Authenticator auth; public FileStore(File storage, PasswordHasher passwordHasher) throws IOException { @@ -58,33 +55,33 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe } private void cleanUp() { - var now = Instant.now(); - var sessions = json.getJSONObject(SESSIONS); - LOG.log(DEBUG,"cleaning up sessions…"); + var now = Instant.now(); + var sessions = sessions(); + LOG.log(DEBUG, "cleaning up sessions…"); var sessionIds = Set.copyOf(sessions.keySet()); for (var sessionId : sessionIds) { var session = sessions.getJSONObject(sessionId); var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)); if (expiration.isBefore(now)) { sessions.remove(sessionId); - LOG.log(DEBUG,"removed old session {0}.",sessionId); + LOG.log(DEBUG, "removed old session {0}.", sessionId); } } - var authorizations = json.getJSONObject(AUTHORIZATIONS); + var authorizations = json.getJSONObject(AUTHORIZATIONS); var authorizationUsers = Set.copyOf(authorizations.keySet()); - var userIds = list().stream().map(User::uuid).collect(Collectors.toSet()); - for (var userId : authorizationUsers){ + var userIds = list().stream().map(User::uuid).collect(Collectors.toSet()); + for (var userId : authorizationUsers) { if (!userIds.contains(userId)) { authorizations.remove(userId); continue; } - var clients = authorizations.getJSONObject(userId); + var clients = authorizations.getJSONObject(userId); var clientIds = Set.copyOf(clients.keySet()); - for (var clientId : clientIds){ + for (var clientId : clientIds) { var client = clients.getJSONObject(clientId); var scopes = Set.copyOf(client.keySet()); - for (var scope : scopes){ + for (var scope : scopes) { var expiration = Instant.ofEpochSecond(client.getLong(scope)); if (expiration.isBefore(now)) { client.remove(scope); @@ -116,6 +113,12 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe return token; } + @Override + public Optional consumeToken(String id) { + var user = forToken(id); + accessTokens.remove(id); + return user; + } @Override public UserService delete(User user) { @@ -126,9 +129,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @Override public Optional forToken(String id) { - AccessToken token = accessTokens.remove(id); + AccessToken token = accessTokens.get(id); if (token == null) return empty(); if (token.valid()) return Optional.of(token.user()); + accessTokens.remove(id); return empty(); } @@ -223,13 +227,13 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @Override public Session createSession(User user) { var now = Instant.now(); - var endOfSession = now.plus(sessionDuration); + var endOfSession = now.plus(user.sessionDuration()); return save(new Session(user, endOfSession, uuid().toString())); } @Override public SessionService dropSession(String sessionId) { - json.getJSONObject(SESSIONS).remove(sessionId); + sessions().remove(sessionId); save(); return this; } @@ -239,11 +243,14 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe return null; } + private JSONObject sessions() { + return json.getJSONObject(SESSIONS); + } + @Override public Optional retrieve(String sessionId) { - var sessions = json.getJSONObject(SESSIONS); try { - var session = sessions.getJSONObject(sessionId); + var session = sessions().getJSONObject(sessionId); var userId = session.getString(USER); var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)); if (expiration.isAfter(Instant.now())) { @@ -256,7 +263,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe } private Session save(Session session) { - json.getJSONObject(SESSIONS).put(session.id(), Map.of(USER, session.user().uuid(), EXPIRATION, session.expiration().getEpochSecond())); + sessions().put(session.id(), Map.of(USER, session.user().uuid(), EXPIRATION, session.expiration().getEpochSecond())); save(); return session; } diff --git a/de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js b/de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js index e7c24e4..2ed2819 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js @@ -1,3 +1,5 @@ +var session_duration_minutes = 10; + function fillForm(){ if (user == null){ setTimeout(fillForm,100); @@ -7,6 +9,9 @@ function fillForm(){ setValue('email',user.email); setValue('uuid', user.uuid); setValue('realname', user.realname); + session_duration_minutes = user.session_duration | 10; + displayDuration(); + } } @@ -64,6 +69,7 @@ async function handleSettings(response){ console.log('handleSettings(…)',response); if (response.ok){ var json = await response.json(); + console.log("json: ",json); for (var key in json){ setValue(key,json[key]); } @@ -125,7 +131,8 @@ function update(){ username : getValue('username'), email : getValue('email'), realname : getValue('realname'), - uuid : getValue('uuid') + uuid : getValue('uuid'), + session_duration : session_duration_minutes } fetch(user_controller+'/update',{ method : 'POST', @@ -137,5 +144,37 @@ function update(){ setText('updateBtn','sent…'); } +function displayDuration(){ + var mins = session_duration_minutes; + hrs = Math.floor(mins/60); + mins-=60*hrs; + days = Math.floor(hrs/24); + hrs-=24*days; + setText('days',days); + setText('hours',hrs); + setText('minutes',mins); +} + +function durationUpdate(){ + var raw = getValue('session_duration'); + console.log(raw); + var mins = 0; + var hrs = 0; + var days = 0; + if (raw<30){ + mins = raw; + } else if(raw<37) { + mins=5*(raw-24); + } else if(raw<57){ + mins=15*(raw-32); + } else if(raw<75){ + mins=60*(raw-50); + } else { + mins=60*24*(raw-73); + } + session_duration_minutes = mins; + displayDuration(); +} + setTimeout(fillForm,100); fetch("/api/email/settings").then(handleSettings); diff --git a/de.srsoftware.oidc.web/src/main/resources/en/settings.html b/de.srsoftware.oidc.web/src/main/resources/en/settings.html index 4ded70a..47d8174 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/settings.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/settings.html @@ -30,12 +30,12 @@ - ID - - - - Error - Failed to update settings! + Session duration + + +
+ days, hours, minutes + @@ -53,8 +53,12 @@ - - + ID + + + + Error + Failed to update settings! New Password diff --git a/de.srsoftware.oidc.web/src/main/resources/en/todo.html b/de.srsoftware.oidc.web/src/main/resources/en/todo.html index b20dcfe..70838d8 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/todo.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/todo.html @@ -12,6 +12,7 @@

to do…