working on user settings
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -2,5 +2,5 @@ package de.srsoftware.oidc.api;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public record Client(String id, String name, Set<String> redirectUris) {
|
public record Client(String id, String name, String secret, Set<String> redirectUris) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* © SRSoftware 2024 */
|
/* © SRSoftware 2024 */
|
||||||
package de.srsoftware.oidc.api;
|
package de.srsoftware.oidc.api;
|
||||||
|
|
||||||
|
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||||
import static java.net.HttpURLConnection.HTTP_OK;
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
@@ -8,6 +9,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 java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -106,13 +108,25 @@ public abstract class PathHandler implements HttpHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
|
public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException {
|
||||||
ex.sendResponseHeaders(HTTP_OK, bytes.length);
|
ex.sendResponseHeaders(status, bytes.length);
|
||||||
ex.getResponseBody().write(bytes);
|
ex.getResponseBody().write(bytes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
|
||||||
|
return sendContent(ex,HTTP_OK,bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean sendContent(HttpExchange ex, Object o) throws IOException {
|
public static boolean sendContent(HttpExchange ex, Object o) throws IOException {
|
||||||
return sendContent(ex, o.toString().getBytes(UTF_8));
|
return sendContent(ex, HTTP_OK, o.toString().getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean sendError(HttpExchange ex, byte[] bytes) throws IOException {
|
||||||
|
return sendContent(ex,HTTP_BAD_REQUEST,bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean sendError(HttpExchange ex, Object o) throws IOException {
|
||||||
|
return sendContent(ex,HTTP_BAD_REQUEST,o.toString().getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public final class User {
|
|||||||
public static final String PERMISSIONS = "permissions";
|
public static final String PERMISSIONS = "permissions";
|
||||||
public static final String REALNAME = "realname";
|
public static final String REALNAME = "realname";
|
||||||
public static final String USERNAME = "username";
|
public static final String USERNAME = "username";
|
||||||
|
public static final String UUID = "uuid";
|
||||||
|
|
||||||
private final Set<Permission> permissions = new HashSet<>();
|
private final Set<Permission> permissions = new HashSet<>();
|
||||||
|
|
||||||
@@ -65,8 +66,8 @@ public final class User {
|
|||||||
|
|
||||||
public Map<String, Object> map(boolean includePassword) {
|
public Map<String, Object> map(boolean includePassword) {
|
||||||
return includePassword
|
return includePassword
|
||||||
? Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, PASSWORD, hashedPassword)
|
? Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid, PASSWORD, hashedPassword)
|
||||||
: Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions);
|
: Map.of(USERNAME, username, REALNAME, realName, EMAIL, email, PERMISSIONS, permissions, UUID, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String realName() {
|
public String realName() {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface UserService {
|
public interface UserService {
|
||||||
public UserService delete(User user);
|
public UserService delete(User user);
|
||||||
|
public boolean passwordMatches(String password, String hashedPassword);
|
||||||
public UserService init(User defaultUser);
|
public UserService init(User defaultUser);
|
||||||
public List<User> list();
|
public List<User> list();
|
||||||
public Optional<User> load(String id);
|
public Optional<User> load(String id);
|
||||||
public Optional<User> load(String username, String password);
|
public Optional<User> load(String username, String password);
|
||||||
public <T extends UserService> T save(User user);
|
public <T extends UserService> T save(User user);
|
||||||
|
public <T extends UserService> T updatePassword(User user, String plaintextPassword);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
package de.srsoftware.oidc.backend;
|
package de.srsoftware.oidc.backend;
|
||||||
|
|
||||||
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
|
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
|
||||||
import static de.srsoftware.oidc.api.User.PASSWORD;
|
import static de.srsoftware.oidc.api.User.*;
|
||||||
import static de.srsoftware.oidc.api.User.USERNAME;
|
|
||||||
import static java.net.HttpURLConnection.*;
|
import static java.net.HttpURLConnection.*;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
@@ -11,6 +10,7 @@ import com.sun.net.httpserver.HttpExchange;
|
|||||||
import de.srsoftware.cookies.SessionToken;
|
import de.srsoftware.cookies.SessionToken;
|
||||||
import de.srsoftware.oidc.api.*;
|
import de.srsoftware.oidc.api.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -83,6 +83,10 @@ public class Backend extends PathHandler {
|
|||||||
return authorize(ex,session);
|
return authorize(ex,session);
|
||||||
case "/clients":
|
case "/clients":
|
||||||
return clients(ex,session);
|
return clients(ex,session);
|
||||||
|
case "/update/password":
|
||||||
|
return updatePassword(ex,session);
|
||||||
|
case "/update/user":
|
||||||
|
return updateUser(ex,session);
|
||||||
case "/user":
|
case "/user":
|
||||||
return sendUserAndCookie(ex, session);
|
return sendUserAndCookie(ex, session);
|
||||||
}
|
}
|
||||||
@@ -114,4 +118,35 @@ public class Backend extends PathHandler {
|
|||||||
out.write(bytes);
|
out.write(bytes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean updatePassword(HttpExchange ex, Session session) throws IOException {
|
||||||
|
var user =session.user();
|
||||||
|
var json = json(ex);
|
||||||
|
var uuid = json.getString(UUID);
|
||||||
|
if (!uuid.equals(user.uuid())) {
|
||||||
|
return sendEmptyResponse(HTTP_FORBIDDEN, ex);
|
||||||
|
}
|
||||||
|
var oldPass = json.getJSONArray("oldpass");
|
||||||
|
var oldPass1 = oldPass.getString(0);
|
||||||
|
if (!oldPass1.equals(oldPass.getString(1))){
|
||||||
|
return sendError(ex,"password mismatch");
|
||||||
|
}
|
||||||
|
if (!users.passwordMatches(oldPass1,user.hashedPassword())) return sendError(ex,"wrong password");
|
||||||
|
users.updatePassword(user,json.getString("newpass"));
|
||||||
|
return sendContent(ex,new JSONObject(user.map(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateUser(HttpExchange ex, Session session) throws IOException {
|
||||||
|
var user =session.user();
|
||||||
|
var json = json(ex);
|
||||||
|
var uuid = json.getString(UUID);
|
||||||
|
if (!uuid.equals(user.uuid())){
|
||||||
|
return sendEmptyResponse(HTTP_FORBIDDEN,ex);
|
||||||
|
}
|
||||||
|
user.username(json.getString(USERNAME));
|
||||||
|
user.email(json.getString(EMAIL));
|
||||||
|
users.save(user);
|
||||||
|
JSONObject response = new JSONObject(user.map(false));
|
||||||
|
return sendContent(ex,response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ public class FileStore implements ClientService, SessionService, UserService {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<User> list() {
|
public List<User> list() {
|
||||||
return List.of();
|
return List.of();
|
||||||
@@ -98,6 +100,11 @@ public class FileStore implements ClientService, SessionService, UserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean passwordMatches(String password, String hashedPassword) {
|
||||||
|
return passwordHasher.matches(password,hashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileStore save(User user) {
|
public FileStore save(User user) {
|
||||||
JSONObject users;
|
JSONObject users;
|
||||||
@@ -110,6 +117,14 @@ public class FileStore implements ClientService, SessionService, UserService {
|
|||||||
return save();
|
return save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileStore updatePassword(User user, String plaintextPassword) {
|
||||||
|
var oldHashedPassword = user.hashedPassword();
|
||||||
|
var salt = passwordHasher.salt(oldHashedPassword);
|
||||||
|
user.hashedPassword(passwordHasher.hash(plaintextPassword,salt));
|
||||||
|
return save(user);
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<User> userOf(JSONObject json, String userId){
|
private Optional<User> userOf(JSONObject json, String userId){
|
||||||
var user = new User(json.getString(USERNAME), json.getString(PASSWORD), json.getString(REALNAME), json.getString(EMAIL), userId);
|
var user = new User(json.getString(USERNAME), json.getString(PASSWORD), json.getString(REALNAME), json.getString(EMAIL), userId);
|
||||||
var perms = json.getJSONArray(PERMISSIONS);
|
var perms = json.getJSONArray(PERMISSIONS);
|
||||||
@@ -132,7 +147,7 @@ public class FileStore implements ClientService, SessionService, UserService {
|
|||||||
public Session createSession(User user) {
|
public Session createSession(User user) {
|
||||||
var now = Instant.now();
|
var now = Instant.now();
|
||||||
var endOfSession = now.plus(sessionDuration);
|
var endOfSession = now.plus(sessionDuration);
|
||||||
return save(new Session(user, endOfSession, UUID.randomUUID().toString()));
|
return save(new Session(user, endOfSession, java.util.UUID.randomUUID().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,3 +2,29 @@ var api = "/api";
|
|||||||
var web = "/web";
|
var web = "/web";
|
||||||
|
|
||||||
const UNAUTHORIZED = 401;
|
const UNAUTHORIZED = 401;
|
||||||
|
|
||||||
|
function get(id){
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable(id){
|
||||||
|
get(id).setAttribute('disabled',true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable(id){
|
||||||
|
get(id).removeAttribute('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(id){
|
||||||
|
return get(id).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setText(id, text){
|
||||||
|
get(id).innerHTML = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setValue(id,newVal){
|
||||||
|
document.getElementById(id).value = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
<script src="user.js"></script>
|
<script src="user.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav></nav>
|
||||||
<a id="clients" href="clients.html">Clients</a>
|
|
||||||
</nav>
|
|
||||||
<h1>Welcome!</h1>
|
<h1>Welcome!</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -16,8 +16,8 @@ function doRedirect(){
|
|||||||
|
|
||||||
function tryLogin(){
|
function tryLogin(){
|
||||||
document.getElementById("error").innerHTML = "";
|
document.getElementById("error").innerHTML = "";
|
||||||
var username = document.getElementById('username').value;
|
var username = getValue('username');
|
||||||
var password = document.getElementById('password').value;
|
var password = getValue('password');
|
||||||
fetch(api+"/login",{
|
fetch(api+"/login",{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<a href="index.html">Dashboard</a>
|
||||||
|
<a href="clients.html" class="MANAGE_CLIENTS">Clients</a>
|
||||||
|
<a href="users.html" class="MANAGE_USERS">Users</a>
|
||||||
|
<a href="settings.html">Settings</a>
|
||||||
|
<a href="logout.html">Logout</a>
|
||||||
55
de.srsoftware.oidc.web/src/main/resources/en/settings.html
Normal file
55
de.srsoftware.oidc.web/src/main/resources/en/settings.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Light OIDC</title>
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script src="user.js"></script>
|
||||||
|
<script src="settings.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav></nav>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
Basic settings
|
||||||
|
</legend>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>User name</th>
|
||||||
|
<td><input id="username" type="text"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Email</th>
|
||||||
|
<td><input id="email" type="email"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<td><input id="uuid" type="text" disabled="true"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button id="updateBtn" type="button" onClick="update()">Update</button>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
Password
|
||||||
|
</legend>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Old password</th>
|
||||||
|
<td><input id="oldpass1" type="password"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Repeat Password</th>
|
||||||
|
<td><input id="oldpass2" type="password"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>New Password</th>
|
||||||
|
<td><input id="newpass" type="password"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button id="passBtn" type="button" onClick="updatePass()">Update</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
68
de.srsoftware.oidc.web/src/main/resources/en/settings.js
Normal file
68
de.srsoftware.oidc.web/src/main/resources/en/settings.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
function fillForm(){
|
||||||
|
if (user == null){
|
||||||
|
setTimeout(fillForm,100);
|
||||||
|
} else {
|
||||||
|
console.log(user);
|
||||||
|
setValue('username',user.username);
|
||||||
|
setValue('email',user.email);
|
||||||
|
setValue('uuid', user.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleResponse(response){
|
||||||
|
setText('updateBtn',response.ok ? 'saved.' : 'failed!');
|
||||||
|
setTimeout(function(){
|
||||||
|
setText('updateBtn','Update');
|
||||||
|
enable('updateBtn');
|
||||||
|
},10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePasswordResponse(response){
|
||||||
|
setText('passBtn',response.ok ? 'saved.' : 'failed!');
|
||||||
|
setTimeout(function(){
|
||||||
|
setText('passBtn','Update');
|
||||||
|
enable('passBtn');
|
||||||
|
},10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
disable('updateBtn');
|
||||||
|
setText('updateBtn','sent…');
|
||||||
|
var newData = {
|
||||||
|
username : getValue('username'),
|
||||||
|
email : getValue('email'),
|
||||||
|
uuid : getValue('uuid')
|
||||||
|
}
|
||||||
|
fetch(api+'/update/user',{
|
||||||
|
method : 'POST',
|
||||||
|
headers : {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body : JSON.stringify(newData)
|
||||||
|
}).then(handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePass(){
|
||||||
|
disable('passBtn');
|
||||||
|
setText('passBtn','sent…');
|
||||||
|
var newData = {
|
||||||
|
oldpass : [getValue('oldpass1'),getValue('oldpass2')],
|
||||||
|
newpass : getValue('newpass'),
|
||||||
|
uuid : getValue('uuid')
|
||||||
|
}
|
||||||
|
fetch(api+'/update/password',{
|
||||||
|
method : 'POST',
|
||||||
|
headers : {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body : JSON.stringify(newData)
|
||||||
|
}).then(handlePasswordResponse);
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
setText('passBtn','Update');
|
||||||
|
enable('passBtn');
|
||||||
|
},10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(fillForm,100);
|
||||||
@@ -1,11 +1,27 @@
|
|||||||
|
var user = null;
|
||||||
async function handleUser(response){
|
async function handleUser(response){
|
||||||
if (response.status == UNAUTHORIZED) {
|
if (response.status == UNAUTHORIZED) {
|
||||||
window.location.href = 'login.html?return_to='+encodeURI(window.location.href);
|
window.location.href = 'login.html?return_to='+encodeURI(window.location.href);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var user = await response.json();
|
if (response.ok){
|
||||||
// TODO: load navigation
|
user = await response.json();
|
||||||
|
fetch(web+"/navigation.html").then(handleNavigation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleNavigation(response){
|
||||||
|
if (response.ok){
|
||||||
|
var content = await response.text();
|
||||||
|
var nav = document.getElementsByTagName('nav')[0];
|
||||||
|
nav.innerHTML = content;
|
||||||
|
var links = nav.getElementsByTagName('a');
|
||||||
|
for (var index = 0; index < links.length; index++){
|
||||||
|
var link = links[index];
|
||||||
|
var clazz = link.hasAttribute('class') ? link.getAttribute("class") : null;
|
||||||
|
if (clazz != null && !user.permissions.includes(clazz)) nav.removeChild(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(api+"/user",{method:'POST'}).then(handleUser);
|
fetch(api+"/user",{method:'POST'}).then(handleUser);
|
||||||
Reference in New Issue
Block a user