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.Headers;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
@@ -15,11 +14,16 @@ 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");
|
private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss O");
|
||||||
|
|
||||||
public SessionToken(String sessionId, Instant expiration){
|
public SessionToken(String sessionId, Instant expiration, boolean trust) {
|
||||||
super("sessionToken", "%s; Path=/api; Expires=%s".formatted(sessionId,FORMAT.format(expiration.atZone(ZoneOffset.UTC))));
|
super("sessionToken", sessionToken(sessionId, expiration, trust));
|
||||||
this.sessionId = sessionId;
|
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) {
|
public SessionToken(String sessionId) {
|
||||||
super("sessionToken", sessionId + "; Path=/api");
|
super("sessionToken", sessionId + "; Path=/api");
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class Constants {
|
|||||||
public static final String START_TLS = "start_tls";
|
public static final String START_TLS = "start_tls";
|
||||||
public static final String TOKEN = "token";
|
public static final String TOKEN = "token";
|
||||||
public static final String TOKEN_TYPE = "token_type";
|
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 UNAUTHORIZED_CLIENT = "unauthorized_client";
|
||||||
public static final String USER = "user";
|
public static final String USER = "user";
|
||||||
public static final String USER_ID = "user_id";
|
public static final String USER_ID = "user_id";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import de.srsoftware.oidc.api.data.User;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface SessionService {
|
public interface SessionService {
|
||||||
Session createSession(User user);
|
Session createSession(User user, boolean trustBrowser);
|
||||||
SessionService dropSession(String sessionId);
|
SessionService dropSession(String sessionId);
|
||||||
Session extend(Session session, User user);
|
Session extend(Session session, User user);
|
||||||
Optional<Session> retrieve(String sessionId);
|
Optional<Session> retrieve(String sessionId);
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package de.srsoftware.oidc.api.data;
|
|||||||
|
|
||||||
import java.time.Instant;
|
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));
|
var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5));
|
||||||
|
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
var session = sessionService().createSession(user);
|
var session = sessionService().createSession(user, false);
|
||||||
var expiration = session.expiration();
|
var expiration = session.expiration();
|
||||||
assertTrue(expiration.isAfter(now.plus(5, ChronoUnit.MINUTES).minusSeconds(1)));
|
assertTrue(expiration.isAfter(now.plus(5, ChronoUnit.MINUTES).minusSeconds(1)));
|
||||||
assertTrue(expiration.isBefore(now.plus(5, ChronoUnit.MINUTES).plusSeconds(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 pass = hasher().hash(PASSWORD, uuid);
|
||||||
var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5));
|
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();
|
Instant now = Instant.now();
|
||||||
sessionService().extend(session, user.sessionDuration(Duration.ofMinutes(10)));
|
sessionService().extend(session, user.sessionDuration(Duration.ofMinutes(10)));
|
||||||
@@ -75,7 +75,7 @@ public abstract class SessionServiceTest {
|
|||||||
var pass = hasher().hash(PASSWORD, uuid);
|
var pass = hasher().hash(PASSWORD, uuid);
|
||||||
var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofMinutes(5));
|
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());
|
assertTrue(sessionService().retrieve(session.id()).isPresent());
|
||||||
|
|
||||||
sessionService().dropSession(session.id());
|
sessionService().dropSession(session.id());
|
||||||
@@ -89,7 +89,7 @@ public abstract class SessionServiceTest {
|
|||||||
var pass = hasher().hash(PASSWORD, uuid);
|
var pass = hasher().hash(PASSWORD, uuid);
|
||||||
var user = new User(USERNAME, pass, REALNAME, EMAIL, uuid).sessionDuration(Duration.ofSeconds(2));
|
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());
|
assertTrue(sessionService().retrieve(session.id()).isPresent());
|
||||||
|
|
||||||
Thread.sleep(2500);
|
Thread.sleep(2500);
|
||||||
|
|||||||
@@ -193,9 +193,10 @@ 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;
|
||||||
|
|
||||||
Optional<User> user = users.load(username, password);
|
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);
|
return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +225,8 @@ public class UserController extends Controller {
|
|||||||
if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token");
|
if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token");
|
||||||
var user = optUser.get();
|
var user = optUser.get();
|
||||||
users.updatePassword(user, newPass);
|
users.updatePassword(user, newPass);
|
||||||
var session = sessions.createSession(user);
|
var session = sessions.createSession(user, false);
|
||||||
new SessionToken(session.id(),session.expiration()).addTo(ex);
|
new SessionToken(session.id(), session.expiration(), session.trustBrowser()).addTo(ex);
|
||||||
return sendRedirect(ex, "/");
|
return sendRedirect(ex, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +267,7 @@ public class UserController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean sendUserAndCookie(HttpExchange ex, Session session, User user) throws IOException {
|
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));
|
return sendContent(ex, user.map(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,10 +221,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
// TODO: drop expired sessions
|
// TODO: drop expired sessions
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Session createSession(User user) {
|
public Session createSession(User user, boolean trustBrowser) {
|
||||||
var now = Instant.now();
|
var now = Instant.now();
|
||||||
var endOfSession = now.plus(user.sessionDuration()).truncatedTo(SECONDS);
|
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
|
@Override
|
||||||
@@ -237,7 +237,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
@Override
|
@Override
|
||||||
public Session extend(Session session, User user) {
|
public Session extend(Session session, User user) {
|
||||||
var endOfSession = Instant.now().plus(user.sessionDuration());
|
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() {
|
private JSONObject sessions() {
|
||||||
@@ -251,7 +251,8 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
var session = sessions().getJSONObject(sessionId);
|
var session = sessions().getJSONObject(sessionId);
|
||||||
var userId = session.getString(USER);
|
var userId = session.getString(USER);
|
||||||
var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)).truncatedTo(SECONDS);
|
var expiration = Instant.ofEpochSecond(session.getLong(EXPIRATION)).truncatedTo(SECONDS);
|
||||||
if (expiration.isAfter(Instant.now())) return Optional.of(new Session(userId, expiration, sessionId));
|
var trustBrowser = session.getBoolean(TRUST);
|
||||||
|
if (expiration.isAfter(Instant.now())) return Optional.of(new Session(userId, expiration, sessionId, trustBrowser));
|
||||||
dropSession(sessionId);
|
dropSession(sessionId);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
@@ -259,7 +260,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Session save(Session session) {
|
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();
|
save();
|
||||||
return session;
|
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 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 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 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) VALUES (?,?,?) ON CONFLICT DO UPDATE SET expiration = ?;";
|
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 DROP_SESSION = "DELETE FROM sessions WHERE id = ?";
|
||||||
private static final String SELECT_SESSION = "SELECT * 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
|
@Override
|
||||||
public Session createSession(User user) {
|
public Session createSession(User user, boolean trustBrowser) {
|
||||||
var now = Instant.now();
|
var now = Instant.now();
|
||||||
var endOfSession = now.plus(user.sessionDuration()).truncatedTo(SECONDS);
|
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 {
|
private void createStoreTables() throws SQLException {
|
||||||
@@ -53,7 +53,7 @@ public class SqliteSessionService extends SqliteStore implements SessionService
|
|||||||
@Override
|
@Override
|
||||||
public Session extend(Session session, User user) {
|
public Session extend(Session session, User user) {
|
||||||
var endOfSession = Instant.now().plus(user.sessionDuration());
|
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
|
@Override
|
||||||
@@ -101,7 +101,8 @@ public class SqliteSessionService extends SqliteStore implements SessionService
|
|||||||
if (rs.next()) {
|
if (rs.next()) {
|
||||||
var userID = rs.getString("userId");
|
var userID = rs.getString("userId");
|
||||||
var expiration = Instant.ofEpochSecond(rs.getLong("expiration"));
|
var expiration = Instant.ofEpochSecond(rs.getLong("expiration"));
|
||||||
if (expiration.isAfter(Instant.now())) result = Optional.of(new Session(userID, expiration, sessionId));
|
var trustBrowser = rs.getBoolean("trust_browser");
|
||||||
|
if (expiration.isAfter(Instant.now())) result = Optional.of(new Session(userID, expiration, sessionId, trustBrowser));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
return result;
|
return result;
|
||||||
@@ -117,7 +118,9 @@ public class SqliteSessionService extends SqliteStore implements SessionService
|
|||||||
stmt.setString(1, session.id());
|
stmt.setString(1, session.id());
|
||||||
stmt.setString(2, session.userId());
|
stmt.setString(2, session.userId());
|
||||||
stmt.setLong(3, expiration);
|
stmt.setLong(3, expiration);
|
||||||
stmt.setLong(4, expiration);
|
stmt.setBoolean(4, session.trustBrowser());
|
||||||
|
stmt.setLong(5, expiration);
|
||||||
|
stmt.setBoolean(6, session.trustBrowser());
|
||||||
stmt.execute();
|
stmt.execute();
|
||||||
return session;
|
return session;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="trust" checked="checked"/>
|
<input type="checkbox" id="trust" name="trust" checked="checked"/>
|
||||||
Quit session when browser is closed.
|
Quit session when browser is closed.
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ function resetPw(){
|
|||||||
function tryLogin(){
|
function tryLogin(){
|
||||||
var username = getValue('username');
|
var username = getValue('username');
|
||||||
var password = getValue('password');
|
var password = getValue('password');
|
||||||
|
var trust = !get('trust').checked;
|
||||||
fetch(user_controller+"/login",{
|
fetch(user_controller+"/login",{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -54,7 +55,8 @@ function tryLogin(){
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username : username,
|
username : username,
|
||||||
password : password
|
password : password,
|
||||||
|
trust: trust
|
||||||
})
|
})
|
||||||
}).then(handleLogin);
|
}).then(handleLogin);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<li>implement token refresh</li>
|
<li>implement token refresh</li>
|
||||||
<li>Verschlüsselung im config-File</li>
|
<li>Verschlüsselung im config-File</li>
|
||||||
<li>Configuration im Frontend</li>
|
<li>Configuration im Frontend</li>
|
||||||
<li>Process <em>quit session when browser is closed</em> input on login</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user