Browse Source

implemented password update procedure

feature/document
Stephan Richter 4 months ago
parent
commit
2c2eaacd5d
  1. 4
      frontend/src/Components/ClickInput.svelte
  2. 4
      frontend/src/Components/ClickSelect.svelte
  3. 0
      frontend/src/Components/EditableField.svelte
  4. 64
      frontend/src/routes/user/EditPassword.svelte
  5. 17
      frontend/src/routes/user/User.svelte
  6. 2
      frontend/src/translations.svelte.js
  7. 2
      translations/src/main/resources/de.json
  8. 39
      user/src/main/java/de/srsoftware/umbrella/user/UserModule.java
  9. 7
      user/src/main/java/de/srsoftware/umbrella/user/model/Password.java
  10. 37
      web/src/main/resources/web/css/default.css
  11. 10
      web/src/main/resources/web/css/winter.css

4
frontend/src/routes/user/ClickInput.svelte → frontend/src/Components/ClickInput.svelte

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script>
import { t } from '../../translations.svelte.js';
import { checkUser } from '../../user.svelte.js';
import { t } from '../translations.svelte.js';
import { checkUser } from '../user.svelte.js';
let { key, onUpdate, value } = $props();

4
frontend/src/routes/user/ClickSelect.svelte → frontend/src/Components/ClickSelect.svelte

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script>
import { t } from '../../translations.svelte.js';
import { checkUser } from '../../user.svelte.js';
import { t } from '../translations.svelte.js';
import { checkUser } from '../user.svelte.js';
let { fetchOptions, key, value, onUpdate } = $props();

0
frontend/src/routes/user/EditableField.svelte → frontend/src/Components/EditableField.svelte

64
frontend/src/routes/user/EditPassword.svelte

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
<script>
import { t } from '../../translations.svelte.js';
let { editPassword = $bindable() } = $props();
let oldPass = $state("");
let newPass = $state("");
let repeat = $state("");
let oldEmpty = $derived(!/\S/.test(oldPass));
let newEmpty = $derived(!/\S/.test(newPass));
let mismatch = $derived(newPass != repeat);
function abort(){
editPassword = false;
}
async function submit(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/user/password`;
const data = {
old: oldPass,
new: newPass
};
const resp = await fetch(url,{
method: 'PATCH',
body: JSON.stringify(data),
credentials: 'include'
});
if (resp.ok){
const json = await resp.json();
console.log(json);
}
}
</script>
<style>
label{
display: block;
}
</style>
<fieldset class="overlay">
<legend>{t('user.edit_password')}</legend>
<label>
<input type="password" bind:value={oldPass} /> {t('user.old_password')}
{#if oldEmpty}
<span class="error">{t('user.must_not_be_empty')}</span>
{/if}
</label>
<label>
<input type="password" bind:value={newPass} /> {t('user.new_password')}
{#if newEmpty}
<span class="error">{t('user.must_not_be_empty')}</span>
{/if}
</label>
<label>
<input type="password" bind:value={repeat} /> {t('user.repeat_new_password')}
{#if mismatch}
<span class="error">{t('user.mismatch')}</span>
{/if}
</label>
<button onclick={submit} disabled={oldEmpty||newEmpty||mismatch}>{t('user.update')}</button>
<button onclick={abort}>{t('user.abort')}</button>
</fieldset>

17
frontend/src/routes/user/User.svelte

@ -1,9 +1,10 @@ @@ -1,9 +1,10 @@
<script>
import { t } from '../../translations.svelte.js';
import { user } from '../../user.svelte.js';
import EditableField from './EditableField.svelte';
import ClickInput from './ClickInput.svelte';
import ClickSelect from './ClickSelect.svelte';
import ClickInput from '../../Components/ClickInput.svelte';
import ClickSelect from '../../Components/ClickSelect.svelte';
import EditPassword from './EditPassword.svelte';
let editPassword = false;
async function patch(changeset){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/user/${user.id}`;
@ -65,6 +66,16 @@ @@ -65,6 +66,16 @@
<ClickSelect key='theme' value={user.theme} fetchOptions={fetchThemes} onUpdate={patch} />
</td>
</tr>
<tr>
<th>{t('user.password')}</th>
<td>
{#if editPassword}
<EditPassword bind:editPassword={editPassword} />
{:else}
<button onclick={() => editPassword = true}>{t('user.edit_password')}</button>
{/if}
</td>
</tr>
<tr>
<th>{t('user.permissions')}</th>
<td>

2
frontend/src/translations.svelte.js

@ -13,7 +13,7 @@ export function t(key,...args){ @@ -13,7 +13,7 @@ export function t(key,...args){
for (let token of keys){
if (!set[token]){
console.log('Missing translation for '+key);
return keys[keys.length-1].replace('_',' ');
return keys[keys.length-1].replaceAll('_',' ');
}
set = set[token];
}

2
translations/src/main/resources/de.json

@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
"user" : {
"CREATE_USERS": "NUTZER ANLEGEN",
"DELETE_USERS": "NUTZER LÖSCHEN",
"edit_password": "Passwort ändern",
"email": "E-Mail",
"id": "Id",
"IMPERSONATE": "NUTZER WECHSELN",
@ -28,6 +29,7 @@ @@ -28,6 +29,7 @@
"login": "Login",
"MANAGE_LOGIN_SERVICES": "LOGIN-SERVICES VERWALTEN",
"name": "Name",
"password": "Passwort",
"permissions": "Berechtigungen",
"profile": "Profil",
"theme": "Design",

39
user/src/main/java/de/srsoftware/umbrella/user/UserModule.java

@ -26,6 +26,7 @@ import java.time.Instant; @@ -26,6 +26,7 @@ import java.time.Instant;
import java.util.List;
import java.util.Set;
import org.json.JSONObject;
import org.sqlite.core.DB;
public class UserModule extends PathHandler {
@ -93,6 +94,7 @@ public class UserModule extends PathHandler { @@ -93,6 +94,7 @@ public class UserModule extends PathHandler {
long userId;
try {
if (head == null || head.isBlank()) return sendContent(ex,UNPROCESSABLE,"User id missing!");
if (PASSWORD.equals(head)) return patchPassword(ex,requestingUser);
userId = Long.parseLong(head);
} catch (NumberFormatException e) {
return sendContent(ex,UNPROCESSABLE,"Invalid user id: "+head);
@ -125,6 +127,29 @@ public class UserModule extends PathHandler { @@ -125,6 +127,29 @@ public class UserModule extends PathHandler {
}
}
private boolean patchPassword(HttpExchange ex, UmbrellaUser requestingUser) throws IOException {
if (!(requestingUser instanceof DbUser user)) return sendContent(ex,SERVER_ERROR,"DbUser expected");
JSONObject json;
try {
json = json(ex);
} catch (Exception e){
LOG.log(WARNING,"Request does not contain valid JSON",e);
return sendContent(ex,BAD_REQUEST,"Body contains no JSON data");
}
if (!json.has("old") || !(json.get("old") instanceof String oldpass) || oldpass.isBlank()) return sendContent(ex,UNPROCESSABLE,"old password missing!");
if (!json.has("new") || !(json.get("new") instanceof String newpass) || newpass.isBlank()) return sendContent(ex,UNPROCESSABLE,"new password missing!");
var old = Password.of(BAD_HASHER.hash(oldpass,null));
if (!user.hashedPassword().equals(old)) return sendContent(ex,UNAUTHORIZED,"Wrong password (old)");
if (weak(newpass)) return sendContent(ex,BAD_REQUEST,"New password too weak!");
var pass = Password.of(BAD_HASHER.hash(newpass,null));
try {
var updated = users.save(new DbUser(user.id(), user.name(), user.email(), pass, user.theme(), user.language(), user.permissions(), null));
return sendContent(ex, updated);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
}
}
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
addCors(ex);
@ -191,4 +216,18 @@ public class UserModule extends PathHandler { @@ -191,4 +216,18 @@ public class UserModule extends PathHandler {
var saved = users.save(new DbUser(id,name,email,pass,theme,lang, user.permissions(),null));
return sendContent(ex,OK,saved);
}
static int score(String password){
if (password == null) return 0;
var score = 0;
for (int i=0; i<password.length(); i++){
int c = password.charAt(i);
score += Character.isDigit(c) || Character.isLetter(c) ? 1 : 3;
}
return score;
}
private static boolean weak(String password){
return score(password) < 14;
};
}

7
user/src/main/java/de/srsoftware/umbrella/user/model/Password.java

@ -9,6 +9,13 @@ public class Password implements CharSequence{ @@ -9,6 +9,13 @@ public class Password implements CharSequence{
this.pass = val;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof String s) return s.equals(pass);
if (!(obj instanceof Password other)) return false;
return pass != null && pass.equals(other.pass);
}
@Override
public int length() {
return pass.length();

37
web/src/main/resources/web/css/default.css

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
a {
color: orange;
}
body {
background: black;
color: orange;
}
fieldset {
border: 1px solid orange;
border-radius: 4px;
}
input{
background: black;
border: 1px dotted orange;
border-radius: 4px;
padding: 3px;
margin: 3px;
color: orange;
}
button{
background: orange;
border-radius: 5px;
padding: 5px 7px;
border-width: 2px;
border-style: solid;
border-color: yellow red red yellow;
}
footer {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
margin: 5px;
}

10
web/src/main/resources/web/css/winter.css

@ -1,11 +1,17 @@ @@ -1,11 +1,17 @@
a {
color: black;
}
body {
background: white;
color: navy;
}
button:disabled{
color:lightgray;
border-color:lightgray;
}
fieldset {
border: 1px solid blue;
border-radius: 4px;
@ -21,12 +27,12 @@ input{ @@ -21,12 +27,12 @@ input{
}
button{
background: red;
background: snow;
border-radius: 5px;
padding: 5px 7px;
border-width: 2px;
border-style: solid;
border-color: yellow red red yellow;
border-color: darkgray black black darkgray;
}
footer {
position: absolute;

Loading…
Cancel
Save