diff --git a/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java b/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java index bac9916..d0de4a8 100644 --- a/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java +++ b/de.srsoftware.http/src/main/java/de/srsoftware/http/SessionToken.java @@ -4,7 +4,6 @@ package de.srsoftware.http; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; - import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -12,14 +11,19 @@ import java.util.Optional; public class SessionToken extends Cookie { - private final String sessionId; + private final String sessionId; private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss O"); - public SessionToken(String sessionId, Instant expiration){ - super("sessionToken", "%s; Path=/api; Expires=%s".formatted(sessionId,FORMAT.format(expiration.atZone(ZoneOffset.UTC)))); + public SessionToken(String sessionId, Instant expiration, boolean trust) { + super("sessionToken", sessionToken(sessionId, expiration, trust)); this.sessionId = sessionId; } + private static String sessionToken(String sessionId, Instant expiration, boolean trust) { + if (trust) return "%s; Path=/api; Expires=%s".formatted(sessionId, FORMAT.format(expiration.atZone(ZoneOffset.UTC))); + return "%s; Path=/api".formatted(sessionId); + } + public SessionToken(String sessionId) { super("sessionToken", sessionId + "; Path=/api"); this.sessionId = sessionId; 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 d2ce230..d38b7d1 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 @@ -51,6 +51,7 @@ public class Constants { public static final String START_TLS = "start_tls"; public static final String TOKEN = "token"; public static final String TOKEN_TYPE = "token_type"; + public static final String TRUST = "trust"; public static final String UNAUTHORIZED_CLIENT = "unauthorized_client"; public static final String USER = "user"; public static final String USER_ID = "user_id"; diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java index 0667030..c11ff96 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java @@ -6,7 +6,7 @@ import de.srsoftware.oidc.api.data.User; import java.util.Optional; public interface SessionService { - Session createSession(User user); + Session createSession(User user, boolean trustBrowser); SessionService dropSession(String sessionId); Session extend(Session session, User user); Optional retrieve(String sessionId); diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Session.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Session.java index 723d571..1d8700c 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Session.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Session.java @@ -3,5 +3,5 @@ package de.srsoftware.oidc.api.data; import java.time.Instant; -public record Session(String userId, Instant expiration, String id) { +public record Session(String userId, Instant expiration, String id, boolean trustBrowser) { } diff --git a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java index e9ca81d..53d160d 100644 --- a/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java +++ b/de.srsoftware.oidc.api/src/test/java/de/srsoftware/oidc/api/SessionServiceTest.java @@ -41,7 +41,7 @@ public abstract class SessionServiceTest { var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5)); Instant now = Instant.now(); - var session = sessionService().createSession(user); + var session = sessionService().createSession(user, false); var expiration = session.expiration(); assertTrue(expiration.isAfter(now.plus(5, ChronoUnit.MINUTES).minusSeconds(1))); assertTrue(expiration.isBefore(now.plus(5, ChronoUnit.MINUTES).plusSeconds(1))); @@ -57,7 +57,7 @@ public abstract class SessionServiceTest { var pass = hasher().hash(PASSWORD, uuid); var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5)); - var session = sessionService().createSession(user); + var session = sessionService().createSession(user, false); Instant now = Instant.now(); sessionService().extend(session, user.sessionDuration(Duration.ofMinutes(10))); @@ -75,7 +75,7 @@ public abstract class SessionServiceTest { var pass = hasher().hash(PASSWORD, uuid); var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5)); - var session = sessionService().createSession(user); + var session = sessionService().createSession(user, false); assertTrue(sessionService().retrieve(session.id()).isPresent()); sessionService().dropSession(session.id()); @@ -89,7 +89,7 @@ public abstract class SessionServiceTest { var pass = hasher().hash(PASSWORD, uuid); var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofSeconds(2)); - var session = sessionService().createSession(user); + var session = sessionService().createSession(user, false); assertTrue(sessionService().retrieve(session.id()).isPresent()); Thread.sleep(2500); 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 fc7c7a1..ae0417b 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 @@ -193,9 +193,10 @@ 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; Optional user = users.load(username, password); - if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get()), user.get()); + if (user.isPresent()) return sendUserAndCookie(ex, sessions.createSession(user.get(), trust), user.get()); return sendEmptyResponse(HTTP_UNAUTHORIZED, ex); } @@ -224,8 +225,8 @@ public class UserController extends Controller { if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token"); var user = optUser.get(); users.updatePassword(user, newPass); - var session = sessions.createSession(user); - new SessionToken(session.id(),session.expiration()).addTo(ex); + var session = sessions.createSession(user, false); + new SessionToken(session.id(), session.expiration(), session.trustBrowser()).addTo(ex); return sendRedirect(ex, "/"); } @@ -266,7 +267,7 @@ public class UserController extends Controller { } private boolean sendUserAndCookie(HttpExchange ex, Session session, User user) throws IOException { - new SessionToken(session.id(),session.expiration()).addTo(ex); + new SessionToken(session.id(), session.expiration(), session.trustBrowser()).addTo(ex); 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 6aa966a..56d388a 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 @@ -221,10 +221,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe // TODO: drop expired sessions @Override - public Session createSession(User user) { + public Session createSession(User user, boolean trustBrowser) { var now = Instant.now(); var endOfSession = now.plus(user.sessionDuration()).truncatedTo(SECONDS); - return save(new Session(user.uuid(), endOfSession, uuid())); + return save(new Session(user.uuid(), endOfSession, uuid(), trustBrowser)); } @Override @@ -237,7 +237,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @Override public Session extend(Session session, User user) { var endOfSession = Instant.now().plus(user.sessionDuration()); - return save(new Session(user.uuid(), endOfSession, session.id())); + return save(new Session(user.uuid(), endOfSession, session.id(), session.trustBrowser())); } private JSONObject sessions() { @@ -248,10 +248,11 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @Override public Optional retrieve(String sessionId) { try { - var session = sessions().getJSONObject(sessionId); - var userId = session.getString(USER); - var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)).truncatedTo(SECONDS); - if (expiration.isAfter(Instant.now())) return Optional.of(new Session(userId, expiration, sessionId)); + var session = sessions().getJSONObject(sessionId); + var userId = session.getString(USER); + var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)).truncatedTo(SECONDS); + var trustBrowser = session.getBoolean(TRUST); + if (expiration.isAfter(Instant.now())) return Optional.of(new Session(userId, expiration, sessionId, trustBrowser)); dropSession(sessionId); } catch (Exception ignored) { } @@ -259,7 +260,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe } private Session save(Session session) { - sessions().put(session.id(), Map.of(USER, session.userId(), EXPIRATION, session.expiration().getEpochSecond())); + sessions().put(session.id(), Map.of(USER, session.userId(), EXPIRATION, session.expiration().getEpochSecond(), TRUST, session.trustBrowser())); save(); return session; } diff --git a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java index 217197a..86f3adc 100644 --- a/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java +++ b/de.srsoftware.oidc.datastore.sqlite/src/main/java/de/srsoftware/oidc/datastore/sqlite/SqliteSessionService.java @@ -18,8 +18,8 @@ public class SqliteSessionService extends SqliteStore implements SessionService private static final String SELECT_STORE_VERSION = "SELECT * FROM metainfo WHERE key = '" + STORE_VERSION + "'"; private static final String SET_STORE_VERSION = "UPDATE metainfo SET value = ? WHERE key = '" + STORE_VERSION + "'"; - private static final String CREATE_SESSION_TABLE = "CREATE TABLE sessions (id VARCHAR(64) PRIMARY KEY, userId VARCHAR(64) NOT NULL, expiration LONG NOT NULL)"; - private static final String SAVE_SESSION = "INSERT INTO sessions (id, userId, expiration) VALUES (?,?,?) ON CONFLICT DO UPDATE SET expiration = ?;"; + private static final String CREATE_SESSION_TABLE = "CREATE TABLE sessions (id VARCHAR(64) PRIMARY KEY, userId VARCHAR(64) NOT NULL, expiration LONG NOT NULL, trust_browser BOOLEAN DEFAULT false)"; + private static final String SAVE_SESSION = "INSERT INTO sessions (id, userId, expiration, trust_browser) VALUES (?,?,?, ?) ON CONFLICT DO UPDATE SET expiration = ?, trust_browser = ?;"; private static final String DROP_SESSION = "DELETE FROM sessions WHERE id = ?"; private static final String SELECT_SESSION = "SELECT * FROM sessions WHERE id = ?"; @@ -28,10 +28,10 @@ public class SqliteSessionService extends SqliteStore implements SessionService } @Override - public Session createSession(User user) { + public Session createSession(User user, boolean trustBrowser) { var now = Instant.now(); var endOfSession = now.plus(user.sessionDuration()).truncatedTo(SECONDS); - return save(new Session(user.uuid(), endOfSession, uuid())); + return save(new Session(user.uuid(), endOfSession, uuid(), trustBrowser)); } private void createStoreTables() throws SQLException { @@ -53,7 +53,7 @@ public class SqliteSessionService extends SqliteStore implements SessionService @Override public Session extend(Session session, User user) { var endOfSession = Instant.now().plus(user.sessionDuration()); - return save(new Session(user.uuid(), endOfSession, session.id())); + return save(new Session(user.uuid(), endOfSession, session.id(), session.trustBrowser())); } @Override @@ -99,9 +99,10 @@ public class SqliteSessionService extends SqliteStore implements SessionService var rs = stmt.executeQuery(); Optional result = Optional.empty(); if (rs.next()) { - var userID = rs.getString("userId"); - var expiration = Instant.ofEpochSecond(rs.getLong("expiration")); - if (expiration.isAfter(Instant.now())) result = Optional.of(new Session(userID, expiration, sessionId)); + var userID = rs.getString("userId"); + var expiration = Instant.ofEpochSecond(rs.getLong("expiration")); + var trustBrowser = rs.getBoolean("trust_browser"); + if (expiration.isAfter(Instant.now())) result = Optional.of(new Session(userID, expiration, sessionId, trustBrowser)); } rs.close(); return result; @@ -117,7 +118,9 @@ public class SqliteSessionService extends SqliteStore implements SessionService stmt.setString(1, session.id()); stmt.setString(2, session.userId()); stmt.setLong(3, expiration); - stmt.setLong(4, expiration); + stmt.setBoolean(4, session.trustBrowser()); + stmt.setLong(5, expiration); + stmt.setBoolean(6, session.trustBrowser()); stmt.execute(); return session; } catch (SQLException e) { diff --git a/de.srsoftware.oidc.web/src/main/resources/en/login.html b/de.srsoftware.oidc.web/src/main/resources/en/login.html index fbed23b..3483b3d 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/login.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/login.html @@ -31,7 +31,7 @@ diff --git a/de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js b/de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js index 4ed74da..d337eb3 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js @@ -46,6 +46,7 @@ function resetPw(){ function tryLogin(){ var username = getValue('username'); var password = getValue('password'); + var trust = !get('trust').checked; fetch(user_controller+"/login",{ method: 'POST', headers: { @@ -54,7 +55,8 @@ function tryLogin(){ }, body: JSON.stringify({ username : username, - password : password + password : password, + trust: trust }) }).then(handleLogin); return false; 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 2220e8e..da6db37 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/todo.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/todo.html @@ -16,7 +16,6 @@
  • implement token refresh
  • Verschlüsselung im config-File
  • Configuration im Frontend
  • -
  • Process quit session when browser is closed input on login