diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Client.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Client.java index fa9e94a..1e8e9aa 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Client.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Client.java @@ -2,5 +2,5 @@ package de.srsoftware.oidc.api; import java.util.Set; -public record Client(String id, String name, Set redirectUris) { +public record Client(String id, String name, String secret, Set redirectUris) { } diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java index 83f7aba..a71ec36 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/PathHandler.java @@ -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)); } } diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/User.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/User.java index 31f96a3..4505a4d 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/User.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/User.java @@ -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 permissions = new HashSet<>(); @@ -65,8 +66,8 @@ public final class User { public Map 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() { diff --git a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java index ebe81aa..fa64a89 100644 --- a/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java +++ b/de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java @@ -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 list(); public Optional load(String id); public Optional load(String username, String password); public T save(User user); + public T updatePassword(User user, String plaintextPassword); } diff --git a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java index f98adb3..25b0d1a 100644 --- a/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java +++ b/de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/Backend.java @@ -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); + } } 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 b64688d..b1ffb9c 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 @@ -62,6 +62,8 @@ public class FileStore implements ClientService, SessionService, UserService { return this; } + + @Override public List 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 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 diff --git a/de.srsoftware.oidc.web/src/main/resources/en/common.js b/de.srsoftware.oidc.web/src/main/resources/en/common.js index eb0b47c..4b11076 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/common.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/common.js @@ -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; +} + diff --git a/de.srsoftware.oidc.web/src/main/resources/en/index.html b/de.srsoftware.oidc.web/src/main/resources/en/index.html index 0a85d28..c9033ee 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/index.html +++ b/de.srsoftware.oidc.web/src/main/resources/en/index.html @@ -6,9 +6,7 @@ - +

Welcome!

\ No newline at end of file diff --git a/de.srsoftware.oidc.web/src/main/resources/en/login.js b/de.srsoftware.oidc.web/src/main/resources/en/login.js index 48f35e5..ba04950 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/login.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/login.js @@ -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: { diff --git a/de.srsoftware.oidc.web/src/main/resources/en/navigation.html b/de.srsoftware.oidc.web/src/main/resources/en/navigation.html new file mode 100644 index 0000000..3d85631 --- /dev/null +++ b/de.srsoftware.oidc.web/src/main/resources/en/navigation.html @@ -0,0 +1,5 @@ +Dashboard +Clients +Users +Settings +Logout diff --git a/de.srsoftware.oidc.web/src/main/resources/en/settings.html b/de.srsoftware.oidc.web/src/main/resources/en/settings.html new file mode 100644 index 0000000..a37c562 --- /dev/null +++ b/de.srsoftware.oidc.web/src/main/resources/en/settings.html @@ -0,0 +1,55 @@ + + + + Light OIDC + + + + + + +

Settings

+
+
+ + Basic settings + + + + + + + + + + + + + + +
User name
Email
ID
+ +
+
+ + Password + + + + + + + + + + + + + + +
Old password
Repeat Password
New Password
+ +
+
+ + \ No newline at end of file diff --git a/de.srsoftware.oidc.web/src/main/resources/en/settings.js b/de.srsoftware.oidc.web/src/main/resources/en/settings.js new file mode 100644 index 0000000..cb36e9f --- /dev/null +++ b/de.srsoftware.oidc.web/src/main/resources/en/settings.js @@ -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); \ No newline at end of file diff --git a/de.srsoftware.oidc.web/src/main/resources/en/user.js b/de.srsoftware.oidc.web/src/main/resources/en/user.js index 47a578e..f053f50 100644 --- a/de.srsoftware.oidc.web/src/main/resources/en/user.js +++ b/de.srsoftware.oidc.web/src/main/resources/en/user.js @@ -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); \ No newline at end of file