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;
|
||||
|
||||
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 */
|
||||
package de.srsoftware.oidc.api;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||
import static java.net.HttpURLConnection.HTTP_OK;
|
||||
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.HttpServer;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -106,13 +108,25 @@ public abstract class PathHandler implements HttpHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean sendContent(HttpExchange ex, byte[] bytes) throws IOException {
|
||||
ex.sendResponseHeaders(HTTP_OK, bytes.length);
|
||||
public static boolean sendContent(HttpExchange ex, int status, byte[] bytes) throws IOException {
|
||||
ex.sendResponseHeaders(status, bytes.length);
|
||||
ex.getResponseBody().write(bytes);
|
||||
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 {
|
||||
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 REALNAME = "realname";
|
||||
public static final String USERNAME = "username";
|
||||
public static final String UUID = "uuid";
|
||||
|
||||
private final Set<Permission> permissions = new HashSet<>();
|
||||
|
||||
@@ -65,8 +66,8 @@ public final class User {
|
||||
|
||||
public Map<String, Object> map(boolean 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);
|
||||
? 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);
|
||||
}
|
||||
|
||||
public String realName() {
|
||||
|
||||
@@ -6,9 +6,11 @@ import java.util.Optional;
|
||||
|
||||
public interface UserService {
|
||||
public UserService delete(User user);
|
||||
public boolean passwordMatches(String password, String hashedPassword);
|
||||
public UserService init(User defaultUser);
|
||||
public List<User> list();
|
||||
public Optional<User> load(String id);
|
||||
public Optional<User> load(String username, String password);
|
||||
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;
|
||||
|
||||
import static de.srsoftware.oidc.api.Permission.MANAGE_CLIENTS;
|
||||
import static de.srsoftware.oidc.api.User.PASSWORD;
|
||||
import static de.srsoftware.oidc.api.User.USERNAME;
|
||||
import static de.srsoftware.oidc.api.User.*;
|
||||
import static java.net.HttpURLConnection.*;
|
||||
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.oidc.api.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -83,6 +83,10 @@ public class Backend extends PathHandler {
|
||||
return authorize(ex,session);
|
||||
case "/clients":
|
||||
return clients(ex,session);
|
||||
case "/update/password":
|
||||
return updatePassword(ex,session);
|
||||
case "/update/user":
|
||||
return updateUser(ex,session);
|
||||
case "/user":
|
||||
return sendUserAndCookie(ex, session);
|
||||
}
|
||||
@@ -114,4 +118,35 @@ public class Backend extends PathHandler {
|
||||
out.write(bytes);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<User> list() {
|
||||
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
|
||||
public FileStore save(User user) {
|
||||
JSONObject users;
|
||||
@@ -110,6 +117,14 @@ public class FileStore implements ClientService, SessionService, UserService {
|
||||
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){
|
||||
var user = new User(json.getString(USERNAME), json.getString(PASSWORD), json.getString(REALNAME), json.getString(EMAIL), userId);
|
||||
var perms = json.getJSONArray(PERMISSIONS);
|
||||
@@ -132,7 +147,7 @@ public class FileStore implements ClientService, SessionService, UserService {
|
||||
public Session createSession(User user) {
|
||||
var now = Instant.now();
|
||||
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
|
||||
|
||||
@@ -2,3 +2,29 @@ var api = "/api";
|
||||
var web = "/web";
|
||||
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a id="clients" href="clients.html">Clients</a>
|
||||
</nav>
|
||||
<nav></nav>
|
||||
<h1>Welcome!</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,8 +16,8 @@ function doRedirect(){
|
||||
|
||||
function tryLogin(){
|
||||
document.getElementById("error").innerHTML = "";
|
||||
var username = document.getElementById('username').value;
|
||||
var password = document.getElementById('password').value;
|
||||
var username = getValue('username');
|
||||
var password = getValue('password');
|
||||
fetch(api+"/login",{
|
||||
method: 'POST',
|
||||
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){
|
||||
if (response.status == UNAUTHORIZED) {
|
||||
window.location.href = 'login.html?return_to='+encodeURI(window.location.href);
|
||||
return;
|
||||
}
|
||||
var user = await response.json();
|
||||
// TODO: load navigation
|
||||
if (response.ok){
|
||||
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);
|
||||
Reference in New Issue
Block a user