implemented trust option
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<Session> retrieve(String sessionId);
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Session> 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;
|
||||
}
|
||||
|
||||
@@ -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<Session> 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) {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label>
|
||||
<input type="checkbox" name="trust" checked="checked"/>
|
||||
<input type="checkbox" id="trust" name="trust" checked="checked"/>
|
||||
Quit session when browser is closed.
|
||||
</label>
|
||||
</td>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<li>implement token refresh</li>
|
||||
<li>Verschlüsselung im config-File</li>
|
||||
<li>Configuration im Frontend</li>
|
||||
<li>Process <em>quit session when browser is closed</em> input on login</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user