diff --git a/main.go b/main.go index d808ea3..67b2fa7 100644 --- a/main.go +++ b/main.go @@ -20,9 +20,9 @@ import ( _ "embed" ) -const VERSION = "v10"; +const VERSION = "v11"; const LISTEN_HOST = "0.0.0.0"; -const DB_SCHEMA = 12 +const DB_SCHEMA = 13 var ( flagPort int @@ -64,22 +64,23 @@ func main() {// {{{ go connectionManager.BroadcastLoop() static = http.FileServer(http.Dir(config.Application.Directories.Static)) - http.HandleFunc("/css_updated", cssUpdateHandler) - http.HandleFunc("/session/create", sessionCreate) - http.HandleFunc("/session/retrieve", sessionRetrieve) + http.HandleFunc("/css_updated", cssUpdateHandler) + http.HandleFunc("/session/create", sessionCreate) + http.HandleFunc("/session/retrieve", sessionRetrieve) http.HandleFunc("/session/authenticate", sessionAuthenticate) - http.HandleFunc("/node/tree", nodeTree) - http.HandleFunc("/node/retrieve", nodeRetrieve) - http.HandleFunc("/node/create", nodeCreate) - http.HandleFunc("/node/update", nodeUpdate) - http.HandleFunc("/node/rename", nodeRename) - http.HandleFunc("/node/delete", nodeDelete) - http.HandleFunc("/node/upload", nodeUpload) - http.HandleFunc("/node/download", nodeDownload) - http.HandleFunc("/node/search", nodeSearch) - http.HandleFunc("/key/retrieve", keyRetrieve) - http.HandleFunc("/key/create", keyCreate) - http.HandleFunc("/key/counter", keyCounter) + http.HandleFunc("/user/password", userPassword) + http.HandleFunc("/node/tree", nodeTree) + http.HandleFunc("/node/retrieve", nodeRetrieve) + http.HandleFunc("/node/create", nodeCreate) + http.HandleFunc("/node/update", nodeUpdate) + http.HandleFunc("/node/rename", nodeRename) + http.HandleFunc("/node/delete", nodeDelete) + http.HandleFunc("/node/upload", nodeUpload) + http.HandleFunc("/node/download", nodeDownload) + http.HandleFunc("/node/search", nodeSearch) + http.HandleFunc("/key/retrieve", keyRetrieve) + http.HandleFunc("/key/create", keyCreate) + http.HandleFunc("/key/counter", keyCounter) http.HandleFunc("/ws", websocketHandler) http.HandleFunc("/", staticHandler) @@ -201,6 +202,37 @@ func sessionAuthenticate(w http.ResponseWriter, r *http.Request) {// {{{ }) }// }}} +func userPassword(w http.ResponseWriter, r *http.Request) {// {{{ + var err error + var ok bool + var session Session + + if session, _, err = ValidateSession(r, true); err != nil { + responseError(w, err) + return + } + + req := struct { + CurrentPassword string + NewPassword string + }{} + if err = parseRequest(r, &req); err != nil { + responseError(w, err) + return + } + + ok, err = session.UpdatePassword(req.CurrentPassword, req.NewPassword) + if err != nil { + responseError(w, err) + return + } + + responseData(w, map[string]interface{}{ + "OK": true, + "CurrentPasswordOK": ok, + }) +}// }}} + func nodeTree(w http.ResponseWriter, r *http.Request) {// {{{ var err error var session Session diff --git a/session.go b/session.go index 2457eb3..53d163e 100644 --- a/session.go +++ b/session.go @@ -91,7 +91,7 @@ func (session *Session) Authenticate(username, password string) (authenticated b FROM public.user WHERE username=$1 AND - password=$2 + password=password_hash(SUBSTRING(password FROM 1 FOR 32), $2::bytea) `, username, password, diff --git a/sql/0013.sql b/sql/0013.sql new file mode 100644 index 0000000..d8fb23d --- /dev/null +++ b/sql/0013.sql @@ -0,0 +1,34 @@ +/* Required for the gen_random_bytes function */ +CREATE EXTENSION pgcrypto; + +CREATE FUNCTION password_hash(salt_hex char(32), pass bytea) +RETURNS char(96) +LANGUAGE plpgsql +AS +$$ +BEGIN + RETURN ( + SELECT + salt_hex || + encode( + sha256( + decode(salt_hex, 'hex') || /* salt in binary */ + pass /* password */ + ), + 'hex' + ) + ); +END; +$$; + +/* Password has to be able to accommodate 96 characters instead of previous 64. + * It can't be char(96), because then the password would be padded to 96 characters. */ +ALTER TABLE public."user" ALTER COLUMN "password" TYPE varchar(96) USING "password"::varchar; + +/* Update all users with salted and hashed passwords */ +UPDATE public.user +SET password = password_hash( encode(gen_random_bytes(16),'hex'), password::bytea); + +/* After the password hashing, all passwords are now hex encoded 32 characters salt and 64 characters hash, + * and the varchar type is not longer necessary. */ +ALTER TABLE public."user" ALTER COLUMN "password" TYPE char(96) USING "password"::varchar; diff --git a/static/css/main.css b/static/css/main.css index 63d21b9..552928f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -55,6 +55,13 @@ button { border: 2px solid #000; box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.5); z-index: 1025; + width: min-content; +} +#menu .section { + padding: 16px 16px 0px 16px; + font-weight: bold; + white-space: nowrap; + color: #c84a37; } #menu .item { padding: 16px; @@ -137,7 +144,7 @@ button { header { display: grid; grid-area: header; - grid-template-columns: min-content 1fr repeat(3, min-content); + grid-template-columns: min-content 1fr repeat(4, min-content); align-items: center; padding: 8px 0px; color: #333c11; @@ -161,17 +168,13 @@ header .name { padding-left: 16px; font-size: 1.25em; } -header .add { - padding-right: 16px; - cursor: pointer; -} -header .add img { - cursor: pointer; - height: 24px; -} +header .search, +header .add, header .keys { padding-right: 16px; } +header .search img, +header .add img, header .keys img { cursor: pointer; height: 24px; @@ -276,6 +279,11 @@ header .menu { margin-bottom: 32px; font-size: 1.5em; } +#notes-version { + margin-top: 64px; + color: #888; + text-align: center; +} #node-content.encrypted { color: #a00; } @@ -538,6 +546,19 @@ header .menu { #app.node.toggle-tree #tree { display: none; } +#profile-settings { + color: #333; + padding: 16px; +} +#profile-settings .passwords { + display: grid; + grid-template-columns: min-content 200px; + grid-gap: 8px 16px; + margin-bottom: 16px; +} +#profile-settings .passwords div { + white-space: nowrap; +} @media only screen and (max-width: 932px) { #app.node { grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank"; diff --git a/static/images/search.svg b/static/images/search.svg new file mode 100644 index 0000000..28b3b5f --- /dev/null +++ b/static/images/search.svg @@ -0,0 +1,107 @@ + + + + diff --git a/static/js/node.mjs b/static/js/node.mjs index 282614c..6f1b4bf 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -57,7 +57,7 @@ export class NodeUI extends Component { case 'node': if(node.ID == 0) { page = html` - ${children.length > 0 ? html`