diff --git a/main.go b/main.go index d808ea3..5553690 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( const VERSION = "v10"; 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/static/css/main.css b/static/css/main.css index d025926..a92b7c7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -541,6 +541,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/js/node.mjs b/static/js/node.mjs index 282614c..a6f2d25 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -86,6 +86,10 @@ export class NodeUI extends Component { page = html`<${Keys} nodeui=${this} />` break + case 'profile-settings': + page = html`<${ProfileSettings} nodeui=${this} />` + break + case 'search': page = html`<${Search} nodeui=${this} />` break @@ -100,9 +104,10 @@ export class NodeUI extends Component {
this.saveNode()}>
document.getElementById('app').classList.toggle('toggle-tree')} />
Notes
-
this.createNode(evt)}>
-
{ evt.stopPropagation(); this.showPage('keys')}}>
- + +
this.createNode(evt)}>
+
{ evt.stopPropagation(); this.showPage('keys')}}>
+
@@ -213,7 +218,11 @@ export class NodeUI extends Component { this.node.value.create(name, nodeID=>this.goToNode(nodeID)) }//}}} saveNode() {//{{{ - let content = this.nodeContent.current.contentDiv.current.value + let nodeContent = this.nodeContent.current + if(this.page.value != 'node' || nodeContent === null) + return + + let content = nodeContent.contentDiv.current.value this.node.value.setContent(content) this.node.value.save(()=>this.props.app.nodeModified.value = false) }//}}} @@ -547,10 +556,14 @@ class Menu extends Component { return html`
nodeui.menu.value = false}>
` @@ -788,4 +801,77 @@ class Search extends Component { }//}}} } +class ProfileSettings extends Component { + render({ nodeui }, {}) {//{{{ + return html` +
+

User settings

+ +

Password

+
+
Current
+ this.keyHandler(evt)} /> + +
New
+ this.keyHandler(evt)} /> + +
Repeat
+ this.keyHandler(evt)} /> +
+ + +
` + }//}}} + componentDidMount() {//{{{ + document.getElementById('current-password').focus() + }//}}} + + keyHandler(evt) {//{{{ + let handled = true + + switch(evt.key.toUpperCase()) { + case 'ENTER': + this.updatePassword() + break + + default: + handled = false + } + + if(handled) { + evt.preventDefault() + evt.stopPropagation() + } + }//}}} + updatePassword() {//{{{ + let curr_pass = document.getElementById('current-password').value + let pass1 = document.getElementById('new-password1').value + let pass2 = document.getElementById('new-password2').value + + try { + if(pass1.length < 4) { + throw new Error('Password has to be at least 4 characters long') + } + + if(pass1 != pass2) { + throw new Error(`Passwords don't match`) + } + + window._app.current.request('/user/password', { + CurrentPassword: curr_pass, + NewPassword: pass1, + }) + .then(res=>{ + if(res.CurrentPasswordOK) + alert('Password is changed successfully') + else + alert('Current password is invalid') + }) + } catch(err) { + alert(err.message) + } + + }//}}} +} + // vim: foldmethod=marker diff --git a/static/less/main.less b/static/less/main.less index 0a54562..f9fa33c 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -615,6 +615,22 @@ header { } +#profile-settings { + color: #333; + padding: 16px; + + .passwords { + display: grid; + grid-template-columns: min-content 200px; + grid-gap: 8px 16px; + margin-bottom: 16px; + + div { + white-space: nowrap; + } + } +} + @media only screen and (max-width: 932px) { #app.node { .layout-crumbs(); diff --git a/user.go b/user.go new file mode 100644 index 0000000..0d9b4ee --- /dev/null +++ b/user.go @@ -0,0 +1,37 @@ +package main + +import ( + // Standard + "database/sql" +) + +func (session Session) UpdatePassword(currPass, newPass string) (ok bool, err error) { + var result sql.Result + var rowsAffected int64 + + result, err = db.Exec(` + UPDATE public.user + SET + password = password_hash( + /* salt in hex */ + ENCODE(gen_random_bytes(16), 'hex'), + + /* password */ + $1::bytea + ) + WHERE + id = $2 AND + password=password_hash(SUBSTRING(password FROM 1 FOR 32), $3::bytea) + RETURNING id + `, + newPass, + session.UserID, + currPass, + ) + + if rowsAffected, err = result.RowsAffected(); err != nil { + return + } + + return rowsAffected > 0, nil +}