Browse Source

implemented adding users, prepared sending reset links

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 12 months ago
parent
commit
f25814cae5
  1. 2
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java
  2. 2
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Permission.java
  3. 20
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java
  4. 3
      de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java
  5. 53
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  6. 50
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  7. 0
      de.srsoftware.oidc.web/src/main/resources/DE/clients.html
  8. 0
      de.srsoftware.oidc.web/src/main/resources/DE/edit_client.html
  9. 0
      de.srsoftware.oidc.web/src/main/resources/DE/login.html
  10. 0
      de.srsoftware.oidc.web/src/main/resources/DE/logout.html
  11. 1
      de.srsoftware.oidc.web/src/main/resources/DE/navigation.html
  12. 0
      de.srsoftware.oidc.web/src/main/resources/DE/new_client.html
  13. 8
      de.srsoftware.oidc.web/src/main/resources/DE/settings.html
  14. 12
      de.srsoftware.oidc.web/src/main/resources/en/login.html
  15. 1
      de.srsoftware.oidc.web/src/main/resources/en/navigation.html
  16. 16
      de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js
  17. 2
      de.srsoftware.oidc.web/src/main/resources/en/scripts/common.js
  18. 32
      de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js
  19. 2
      de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js
  20. 4
      de.srsoftware.oidc.web/src/main/resources/en/scripts/user.js
  21. 68
      de.srsoftware.oidc.web/src/main/resources/en/scripts/users.js
  22. 8
      de.srsoftware.oidc.web/src/main/resources/en/settings.html
  23. 60
      de.srsoftware.oidc.web/src/main/resources/en/style.css
  24. 22
      de.srsoftware.oidc.web/src/main/resources/en/todo.html
  25. 48
      de.srsoftware.oidc.web/src/main/resources/en/users.html

2
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/UserService.java

@ -4,6 +4,7 @@ package de.srsoftware.oidc.api;
import de.srsoftware.oidc.api.data.User; import de.srsoftware.oidc.api.data.User;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
public interface UserService { public interface UserService {
/** /**
@ -22,6 +23,7 @@ public interface UserService {
public Optional<User> forToken(String accessToken); public Optional<User> forToken(String accessToken);
public UserService init(User defaultUser); public UserService init(User defaultUser);
public List<User> list(); public List<User> list();
public Set<User> find(String key);
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 boolean passwordMatches(String password, String hashedPassword); public boolean passwordMatches(String password, String hashedPassword);

2
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/Permission.java

@ -1,4 +1,4 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.api.data; package de.srsoftware.oidc.api.data;
public enum Permission { MANAGE_CLIENTS } public enum Permission { MANAGE_CLIENTS, MANAGE_USERS }

20
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/data/User.java

@ -2,6 +2,7 @@
package de.srsoftware.oidc.api.data; package de.srsoftware.oidc.api.data;
import java.util.*; import java.util.*;
import org.json.JSONObject;
public final class User { public final class User {
public static final String EMAIL = "email"; public static final String EMAIL = "email";
@ -23,8 +24,8 @@ public final class User {
this.uuid = uuid; this.uuid = uuid;
} }
public User add(Permission permission) { public User add(Permission... newPermissions) {
permissions.add(permission); for (var permission : newPermissions) permissions.add(permission);
return this; return this;
} }
@ -68,6 +69,21 @@ public final class User {
return includePassword ? 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); return includePassword ? 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 static Optional<User> of(JSONObject json, String userId) {
var user = new User(json.getString(USERNAME), json.getString(PASSWORD), json.getString(REALNAME), json.getString(EMAIL), userId);
var perms = json.has(PERMISSIONS) ? json.getJSONArray(PERMISSIONS) : Set.of();
for (Object perm : perms) {
try {
if (perm instanceof String s) perm = Permission.valueOf(s);
if (perm instanceof Permission p) user.add(p);
} catch (Exception e) {
e.printStackTrace();
}
}
return Optional.of(user);
}
public String realName() { public String realName() {
return realName; return realName;
} }

3
de.srsoftware.oidc.app/src/main/java/de/srsoftware/oidc/app/Application.java

@ -4,6 +4,7 @@ package de.srsoftware.oidc.app;
import static de.srsoftware.oidc.api.Constants.*; import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.data.Permission.MANAGE_CLIENTS; import static de.srsoftware.oidc.api.data.Permission.MANAGE_CLIENTS;
import static de.srsoftware.oidc.api.data.Permission.MANAGE_USERS;
import static de.srsoftware.utils.Optionals.emptyIfBlank; import static de.srsoftware.utils.Optionals.emptyIfBlank;
import static de.srsoftware.utils.Paths.configDir; import static de.srsoftware.utils.Paths.configDir;
import static de.srsoftware.utils.Strings.uuid; import static de.srsoftware.utils.Strings.uuid;
@ -52,7 +53,7 @@ public class Application {
var keyDir = storageFile.getParentFile().toPath().resolve("keys"); var keyDir = storageFile.getParentFile().toPath().resolve("keys");
var passwordHasher = new UuidHasher(); var passwordHasher = new UuidHasher();
var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID); var firstHash = passwordHasher.hash(FIRST_USER_PASS, FIRST_UUID);
var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS); var firstUser = new User(FIRST_USER, firstHash, FIRST_USER, "%s@internal".formatted(FIRST_USER), FIRST_UUID).add(MANAGE_CLIENTS, MANAGE_USERS);
KeyStorage keyStore = new PlaintextKeyStore(keyDir); KeyStorage keyStore = new PlaintextKeyStore(keyDir);
KeyManager keyManager = new RotatingKeyManager(keyStore); KeyManager keyManager = new RotatingKeyManager(keyStore);
FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser); FileStore fileStore = new FileStore(storageFile, passwordHasher).init(firstUser);

53
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java

@ -1,7 +1,10 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.backend; package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.data.Permission.MANAGE_USERS;
import static de.srsoftware.oidc.api.data.User.*; import static de.srsoftware.oidc.api.data.User.*;
import static de.srsoftware.utils.Strings.uuid;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.*; import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
@ -22,6 +25,15 @@ public class UserController extends Controller {
users = userService; users = userService;
} }
private boolean addUser(HttpExchange ex, Session session) throws IOException {
var user = session.user();
if (!user.hasPermission(MANAGE_USERS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = json(ex);
var newID = uuid();
User.of(json, uuid()).ifPresent(u -> users.updatePassword(u, json.getString(PASSWORD)));
return sendContent(ex, newID);
}
@Override @Override
public boolean doGet(String path, HttpExchange ex) throws IOException { public boolean doGet(String path, HttpExchange ex) throws IOException {
switch (path) { switch (path) {
@ -41,20 +53,14 @@ public class UserController extends Controller {
return notFound(ex); return notFound(ex);
} }
private boolean userInfo(HttpExchange ex) throws IOException {
var optUser = getBearer(ex).flatMap(users::forToken);
if (optUser.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
var user = optUser.get();
var map = Map.of("sub", user.uuid(), "email", user.email());
return sendContent(ex, new JSONObject(map));
}
@Override @Override
public boolean doPost(String path, HttpExchange ex) throws IOException { public boolean doPost(String path, HttpExchange ex) throws IOException {
switch (path) { switch (path) {
case "/login": case "/login":
return login(ex); return login(ex);
case "/reset":
return resetPassword(ex);
} }
var optSession = getSession(ex); var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex); if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
@ -64,6 +70,10 @@ public class UserController extends Controller {
switch (path) { switch (path) {
case "/": case "/":
return sendUserAndCookie(ex, session); return sendUserAndCookie(ex, session);
case "/add":
return addUser(ex, session);
case "/list":
return list(ex, session);
case "/password": case "/password":
return updatePassword(ex, session); return updatePassword(ex, session);
case "/update": case "/update":
@ -72,6 +82,14 @@ public class UserController extends Controller {
return notFound(ex); return notFound(ex);
} }
private boolean list(HttpExchange ex, Session session) throws IOException {
var user = session.user();
if (!user.hasPermission(MANAGE_USERS)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var json = new JSONObject();
users.list().forEach(u -> json.put(u.uuid(), u.map(false)));
return sendContent(ex, json);
}
private boolean login(HttpExchange ex) throws IOException { private boolean login(HttpExchange ex) throws IOException {
var body = json(ex); var body = json(ex);
@ -89,6 +107,16 @@ public class UserController extends Controller {
return sendEmptyResponse(HTTP_OK, ex); return sendEmptyResponse(HTTP_OK, ex);
} }
private boolean resetPassword(HttpExchange ex) throws IOException {
var idOrEmail = body(ex);
users.find(idOrEmail).forEach(this::senPasswordLink);
return sendEmptyResponse(HTTP_OK, ex);
}
private void senPasswordLink(User user) {
LOG.log(WARNING, "Sending password link to {0}", user.email());
}
private boolean sendUserAndCookie(HttpExchange ex, Session session) throws IOException { private boolean sendUserAndCookie(HttpExchange ex, Session session) throws IOException {
new SessionToken(session.id()).addTo(ex); new SessionToken(session.id()).addTo(ex);
return sendContent(ex, session.user().map(false)); return sendContent(ex, session.user().map(false));
@ -122,7 +150,16 @@ public class UserController extends Controller {
} }
user.username(json.getString(USERNAME)); user.username(json.getString(USERNAME));
user.email(json.getString(EMAIL)); user.email(json.getString(EMAIL));
user.realName(json.getString(REALNAME));
users.save(user); users.save(user);
return sendContent(ex, user.map(false)); return sendContent(ex, user.map(false));
} }
private boolean userInfo(HttpExchange ex) throws IOException {
var optUser = getBearer(ex).flatMap(users::forToken);
if (optUser.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
var user = optUser.get();
var map = Map.of("sub", user.uuid(), "email", user.email());
return sendContent(ex, new JSONObject(map));
}
} }

50
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

@ -31,6 +31,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
private static final String SESSIONS = "sessions"; private static final String SESSIONS = "sessions";
private static final String USERS = "users"; private static final String USERS = "users";
private static final String USER = "user"; private static final String USER = "user";
private static final List<String> KEYS = List.of(USERNAME, EMAIL, REALNAME);
private final Path storageFile; private final Path storageFile;
private final JSONObject json; private final JSONObject json;
@ -91,10 +92,24 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
return this; return this;
} }
@Override
public Set<User> find(String key) {
var users = json.getJSONObject(USERS);
var result = new HashSet<User>();
for (var id : users.keySet()) {
var data = users.getJSONObject(id);
if (id.equals(key)) User.of(data, id).ifPresent(result::add);
if (KEYS.stream().map(data::getString).anyMatch(val -> val.equals(key))) User.of(data, id).ifPresent(result::add);
}
return result;
}
@Override @Override
public List<User> list() { public List<User> list() {
return List.of(); var users = json.getJSONObject(USERS);
List<User> result = new ArrayList<>();
for (var uid : users.keySet()) User.of(users.getJSONObject(uid), uid).ifPresent(result::add);
return result;
} }
@ -103,24 +118,23 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
try { try {
var users = json.getJSONObject(USERS); var users = json.getJSONObject(USERS);
var userData = users.getJSONObject(userId); var userData = users.getJSONObject(userId);
return userOf(userData, userId); return User.of(userData, userId);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
return empty(); return empty();
} }
@Override @Override
public Optional<User> load(String username, String password) { public Optional<User> load(String user, String password) {
try { try {
var users = json.getJSONObject(USERS); var users = json.getJSONObject(USERS);
var uuids = users.keySet(); var uuids = users.keySet();
for (String userId : uuids) { for (String userId : uuids) {
var userData = users.getJSONObject(userId); var userData = users.getJSONObject(userId);
if (!userData.getString(USERNAME).equals(username)) continue;
if (KEYS.stream().map(userData::getString).noneMatch(val -> val.equals(user))) continue;
var hashedPass = userData.getString(PASSWORD); var hashedPass = userData.getString(PASSWORD);
if (passwordHasher.matches(password, hashedPass)) { if (passwordHasher.matches(password, hashedPass)) return User.of(userData, userId);
return userOf(userData, userId);
}
} }
return empty(); return empty();
} catch (Exception e) { } catch (Exception e) {
@ -141,34 +155,16 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
} else { } else {
users = json.getJSONObject(USERS); users = json.getJSONObject(USERS);
} }
users.put(user.uuid(), user.map(true)); users.put(user.uuid(), user.map(true));
return save(); return save();
} }
@Override @Override
public FileStore updatePassword(User user, String plaintextPassword) { public FileStore updatePassword(User user, String plaintextPassword) {
var oldHashedPassword = user.hashedPassword(); return save(user.hashedPassword(passwordHasher.hash(plaintextPassword, uuid())));
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);
for (Object perm : perms) {
try {
if (perm instanceof String s) perm = Permission.valueOf(s);
if (perm instanceof Permission p) user.add(p);
} catch (Exception e) {
e.printStackTrace();
}
}
return Optional.of(user);
}
/*** Session Service Methods ***/ /*** Session Service Methods ***/
// TODO: prolong session on user activity // TODO: prolong session on user activity

0
de.srsoftware.oidc.web/src/main/resources/de/clients.html → de.srsoftware.oidc.web/src/main/resources/DE/clients.html

0
de.srsoftware.oidc.web/src/main/resources/de/edit_client.html → de.srsoftware.oidc.web/src/main/resources/DE/edit_client.html

0
de.srsoftware.oidc.web/src/main/resources/de/login.html → de.srsoftware.oidc.web/src/main/resources/DE/login.html

0
de.srsoftware.oidc.web/src/main/resources/de/logout.html → de.srsoftware.oidc.web/src/main/resources/DE/logout.html

1
de.srsoftware.oidc.web/src/main/resources/de/navigation.html → de.srsoftware.oidc.web/src/main/resources/DE/navigation.html

@ -2,4 +2,5 @@
<a href="clients.html" class="MANAGE_CLIENTS">Clients</a> <a href="clients.html" class="MANAGE_CLIENTS">Clients</a>
<a href="users.html" class="MANAGE_USERS">Benutzer</a> <a href="users.html" class="MANAGE_USERS">Benutzer</a>
<a href="settings.html">Einstellungen</a> <a href="settings.html">Einstellungen</a>
<a href="todo.html">TODO</a>
<a href="logout.html">Abmelden</a> <a href="logout.html">Abmelden</a>

0
de.srsoftware.oidc.web/src/main/resources/de/new_client.html → de.srsoftware.oidc.web/src/main/resources/DE/new_client.html

8
de.srsoftware.oidc.web/src/main/resources/de/settings.html → de.srsoftware.oidc.web/src/main/resources/DE/settings.html

@ -21,6 +21,10 @@
<th>Benutzername</th> <th>Benutzername</th>
<td><input id="username" type="text"></td> <td><input id="username" type="text"></td>
</tr> </tr>
<tr>
<th>Anzeigename</th>
<td><input id="realname" type="text"></td>
</tr>
<tr> <tr>
<th>E-Mail</th> <th>E-Mail</th>
<td><input id="email" type="email"></td> <td><input id="email" type="email"></td>
@ -48,6 +52,10 @@
<th>altes Passwort</th> <th>altes Passwort</th>
<td><input id="oldpass" type="password"></td> <td><input id="oldpass" type="password"></td>
</tr> </tr>
<tr>
<th></th>
<td><input type="text" style="visibility: hidden"></td>
</tr>
<tr> <tr>
<th>Neues Passwort</th> <th>Neues Passwort</th>
<td><input id="newpass1" type="password"></td> <td><input id="newpass1" type="password"></td>

12
de.srsoftware.oidc.web/src/main/resources/en/login.html

@ -14,7 +14,10 @@
<table> <table>
<tr> <tr>
<th>User name</th> <th>User name</th>
<td><input type="text" id="username" placeholder="User name *"/></td> <td style="position: relative">
<input type="text" id="username" placeholder="User name *"/>
<span id="bubble" style="display: none">Please enter a username or email to reset your password!</span>
</td>
</tr> </tr>
<tr> <tr>
<th>Password</th> <th>Password</th>
@ -28,8 +31,15 @@
<td></td> <td></td>
<td><button type="button" onClick="tryLogin()">Login</button></td> <td><button type="button" onClick="tryLogin()">Login</button></td>
</tr> </tr>
<tr>
<td></td>
<td><button type="button" class="light" onClick="resetPw()">reset password?</button></td>
</tr>
</table> </table>
</fieldset> </fieldset>
<div id="sent" class="warning" style="display: none">
A link to reset your password has been sent.
</div>
</div> </div>
<div id="ad"> <div id="ad">
This tiny login provider is brought to you by <b>SRSoftware</b>. <a href="https://git.srsoftware.de/StephanRichter/LightOidc">Find out more…</a> This tiny login provider is brought to you by <b>SRSoftware</b>. <a href="https://git.srsoftware.de/StephanRichter/LightOidc">Find out more…</a>

1
de.srsoftware.oidc.web/src/main/resources/en/navigation.html

@ -2,4 +2,5 @@
<a href="clients.html" class="MANAGE_CLIENTS">Clients</a> <a href="clients.html" class="MANAGE_CLIENTS">Clients</a>
<a href="users.html" class="MANAGE_USERS">Users</a> <a href="users.html" class="MANAGE_USERS">Users</a>
<a href="settings.html">Settings</a> <a href="settings.html">Settings</a>
<a href="todo.html">TODO</a>
<a href="logout.html">Logout</a> <a href="logout.html">Logout</a>

16
de.srsoftware.oidc.web/src/main/resources/en/scripts/clients.js

@ -1,3 +1,7 @@
function edit(clientId){
redirect("edit_client.html?id="+clientId);
}
async function handleClients(response){ async function handleClients(response){
if (response.status == UNAUTHORIZED) { if (response.status == UNAUTHORIZED) {
redirect('login.html?return_to='+encodeURI(window.location.href)) redirect('login.html?return_to='+encodeURI(window.location.href))
@ -8,7 +12,13 @@ async function handleClients(response){
for (let id in clients){ for (let id in clients){
var row = document.createElement("tr"); var row = document.createElement("tr");
var client = clients[id]; var client = clients[id];
row.innerHTML = "<td>"+client.name+"</td>\n<td>"+id+"</td>\n<td>"+client.redirect_uris.join("<br/>")+'</td>\n<td><button type="button" onclick="edit(\''+id+'\')">Edit</button><button class="danger" onclick="remove(\''+id+'\')" type="button">Remove</button></td>'; row.innerHTML = `<td>${client.name}</td>
<td>${id}</td>
<td>${client.redirect_uris.join("<br/>")}</td>
<td>
<button type="button" onclick="edit('${id}')">Edit</button>
<button class="danger" onclick="remove('${id}')" type="button">Remove</button>
</td>`;
bottom.parentNode.insertBefore(row,bottom); bottom.parentNode.insertBefore(row,bottom);
} }
} }
@ -27,8 +37,6 @@ function remove(clientId){
} }
} }
function edit(clientId){
redirect("edit_client.html?id="+clientId);
}
fetch(client_controller+"/list",{method:'POST'}).then(handleClients); fetch(client_controller+"/list",{method:'POST'}).then(handleClients);

2
de.srsoftware.oidc.web/src/main/resources/en/scripts/common.js

@ -10,7 +10,7 @@ function get(id){
} }
function disable(id){ function disable(id){
get(id).setAttribute('disabled',true); get(id).setAttribute('disabled','disabled');
} }
function enable(id){ function enable(id){

32
de.srsoftware.oidc.web/src/main/resources/en/scripts/login.js

@ -1,3 +1,9 @@
function doRedirect(){
let params = new URL(document.location.toString()).searchParams;
redirect( params.get("return_to") || 'index.html');
return false;
}
async function handleLogin(response){ async function handleLogin(response){
if (response.ok){ if (response.ok){
var body = await response.json(); var body = await response.json();
@ -8,10 +14,24 @@ async function handleLogin(response){
} }
} }
function doRedirect(){ function keyDown(ev){
let params = new URL(document.location.toString()).searchParams; if (event.keyCode == 13) tryLogin();
redirect( params.get("return_to") || 'index.html'); }
return false;
function resetPw(){
var user = getValue('username');
if (!user) {
show('bubble');
return;
}
hide('bubble');
fetch(user_controller+"/reset",{
method: 'POST',
body:user
}).then(() => {
hide('login');
show('sent');
});
} }
function tryLogin(){ function tryLogin(){
@ -29,8 +49,4 @@ function tryLogin(){
}) })
}).then(handleLogin); }).then(handleLogin);
return false; return false;
}
function keyDown(ev){
if (event.keyCode == 13) tryLogin();
} }

2
de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js

@ -6,6 +6,7 @@ function fillForm(){
setValue('username',user.username); setValue('username',user.username);
setValue('email',user.email); setValue('email',user.email);
setValue('uuid', user.uuid); setValue('uuid', user.uuid);
setValue('realname', user.realname);
} }
} }
@ -29,6 +30,7 @@ function update(){
var newData = { var newData = {
username : getValue('username'), username : getValue('username'),
email : getValue('email'), email : getValue('email'),
realname : getValue('realname'),
uuid : getValue('uuid') uuid : getValue('uuid')
} }
fetch(user_controller+'/update',{ fetch(user_controller+'/update',{

4
de.srsoftware.oidc.web/src/main/resources/en/scripts/user.js

@ -16,8 +16,8 @@ async function handleNavigation(response){
var nav = document.getElementsByTagName('nav')[0]; var nav = document.getElementsByTagName('nav')[0];
nav.innerHTML = content; nav.innerHTML = content;
var links = nav.getElementsByTagName('a'); var links = nav.getElementsByTagName('a');
for (var index = 0; index < links.length; index++){ for (var index = links.length; index > 0; index--){
var link = links[index]; var link = links[index-1];
var clazz = link.hasAttribute('class') ? link.getAttribute("class") : null; var clazz = link.hasAttribute('class') ? link.getAttribute("class") : null;
if (clazz != null && !user.permissions.includes(clazz)) nav.removeChild(link); if (clazz != null && !user.permissions.includes(clazz)) nav.removeChild(link);
} }

68
de.srsoftware.oidc.web/src/main/resources/en/scripts/users.js

@ -0,0 +1,68 @@
function addUser(){
var pw = getValue('pw');
var pw2 = getValue('pw2');
if (pw != pw2) {
show('pw-mismatch');
return;
}
var msg = {
username : getValue('username'),
realname : getValue('realname'),
email : getValue('email'),
password : pw
};
fetch(user_controller+"/add",{
method:'POST',
header: {
'Content-Type':'application/json'
},
body: JSON.stringify(msg)
}).then(() => location.reload())
}
async function handleUsers(response){
if (response.status == UNAUTHORIZED) {
redirect('login.html?return_to='+encodeURI(window.location.href))
return;
}
var users = await response.json();
var bottom = document.getElementById('bottom');
for (let id in users){
var row = document.createElement("tr");
var user = users[id];
row.innerHTML = `<td>${user.username}</td>
<td>${user.realname}</td>
<td>${user.email}</td>
<td>${id}</td>
<td>
<button type="button" onclick="reset_password('${id}')" id="reset-${id}">Reset password</button>
<button class="danger" onclick="remove('${id}')" type="button">Remove</button>
</td>`;
bottom.parentNode.insertBefore(row,bottom);
}
}
function handleRemove(response){
redirect("users.html");
}
function remove(userId){
var message = document.getElementById('message').innerHTML;
if (confirm(message.replace("{}",userId))) {
fetch(user_controller+"/delete",{
method: 'DELETE',
body : JSON.stringify({ userId : userId })
}).then(handleRemove);
}
}
function reset_password(userid){
fetch(user_controller+"/reset",{
method: 'POST',
body:userid
}).then(() => {
disable('reset-'+userid);
});
}
fetch(user_controller+"/list",{method:'POST'}).then(handleUsers);

8
de.srsoftware.oidc.web/src/main/resources/en/settings.html

@ -21,6 +21,10 @@
<th>User name</th> <th>User name</th>
<td><input id="username" type="text"></td> <td><input id="username" type="text"></td>
</tr> </tr>
<tr>
<th>Display name</th>
<td><input id="realname" type="text"></td>
</tr>
<tr> <tr>
<th>Email</th> <th>Email</th>
<td><input id="email" type="email"></td> <td><input id="email" type="email"></td>
@ -48,6 +52,10 @@
<th>Old password</th> <th>Old password</th>
<td><input id="oldpass" type="password"></td> <td><input id="oldpass" type="password"></td>
</tr> </tr>
<tr>
<th></th>
<td><input type="text" style="visibility: hidden"></td>
</tr>
<tr> <tr>
<th>New Password</th> <th>New Password</th>
<td><input id="newpass1" type="password"></td> <td><input id="newpass1" type="password"></td>

60
de.srsoftware.oidc.web/src/main/resources/en/style.css

@ -29,6 +29,30 @@ button {
margin: 2px; margin: 2px;
} }
button:hover{
padding: 9px;
border: 1px solid orange;
}
button[disabled]{
background: rgb(48,48,48);
border: 1px solid gray;
color: gray;
padding: 9px;
}
button.light{
background: rgb(48,48,48);
color: #444;
border: 1px solid #444;
padding: 9px;
}
button.light:hover{
background: indigo;
color: orange;
}
#login button{ #login button{
width: 100%; width: 100%;
margin: 0; margin: 0;
@ -72,8 +96,10 @@ form th{
text-align: right; text-align: right;
} }
fieldset .centered th, .centered,
form .centered th{ .centered input,
.centered th,
.centered th{
text-align: center; text-align: center;
} }
@ -101,4 +127,34 @@ legend {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
}
ul{
display: inline-block;
text-align: left;
}
#bubble {
--c: indigo;
background: var(--c);
color: orange;
padding: .5em 1em;
position: absolute;
top: 16px;
left: 100%;
width: 220px;
border-radius: 0 .25em .25em .25em;
&::after {
content: '';
position: absolute;
border: .5em solid transparent;
border-top-color: var(--c);
border-right-color: var(--c);
right: 100%;
top: 0%;
// magic
transform: skewY(20deg);
transform-origin: 100% 100%;
}
} }

22
de.srsoftware.oidc.web/src/main/resources/en/todo.html

@ -0,0 +1,22 @@
<html>
<head>
<meta charset="utf-8">
<title>Light OIDC</title>
<script src="scripts/common.js"></script>
<script src="scripts/user.js"></script>
<script src="scripts/users.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<nav></nav>
<div id="content">
<h1>to do…</h1>
<ul>
<li><a href="users.html">Users: remove</a></li>
<li><a href="users.html">Users: send password reset link</a></li>
<li><a href="login.html">Login: send password reset link</a></li>
<li><a href="login.html">Login: "remember me" option</a></li>
</ul>
</div>
</body>
</html>

48
de.srsoftware.oidc.web/src/main/resources/en/users.html

@ -0,0 +1,48 @@
<html>
<head>
<meta charset="utf-8">
<title>Light OIDC</title>
<script src="scripts/common.js"></script>
<script src="scripts/user.js"></script>
<script src="scripts/users.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<nav></nav>
<div id="content">
<h1>Users</h1>
<fieldset>
<legend>These are users that are registered with LightOIDC:</legend>
<table class="centered">
<tr>
<th>Username</th>
<th>Display name</th>
<th>Email</th>
<th>ID</th>
<th>Actions</th>
</tr>
<tr id="bottom">
<td>
<input value="" type="text" id="username" placeholder="user name" />
</td>
<td>
<input value="" type="text" id="realname" placeholder="display name" />
</td>
<td>
<input value="" type="text" id="email" placeholder="email address" autocomplete="off" />
</td>
<td>
<input value="" type="password" id="pw" placeholder="password" autocomplete="new-password" />
<input value="" type="password" id="pw2" placeholder="repeat password" autocomplete="repeat-password" />
</td>
<td>
<button onclick="addUser()">Add new user…</button>
</td>
</tr>
</table>
<span class="error" style="display: none" id="pw-mismatch">Passwords do not match!</span>
</fieldset>
</div>
</body>
</html>
Loading…
Cancel
Save