Compare commits
No commits in common. "63a73705707d2ff77cb222723bf5029af6d7e275" and "9681bd26d50babcc9cfdf16e3f1f8be58b4f479e" have entirely different histories.
63a7370570
...
9681bd26d5
66
main.go
66
main.go
@ -20,9 +20,9 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "v11";
|
const VERSION = "v10";
|
||||||
const LISTEN_HOST = "0.0.0.0";
|
const LISTEN_HOST = "0.0.0.0";
|
||||||
const DB_SCHEMA = 13
|
const DB_SCHEMA = 12
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagPort int
|
flagPort int
|
||||||
@ -64,23 +64,22 @@ func main() {// {{{
|
|||||||
go connectionManager.BroadcastLoop()
|
go connectionManager.BroadcastLoop()
|
||||||
|
|
||||||
static = http.FileServer(http.Dir(config.Application.Directories.Static))
|
static = http.FileServer(http.Dir(config.Application.Directories.Static))
|
||||||
http.HandleFunc("/css_updated", cssUpdateHandler)
|
http.HandleFunc("/css_updated", cssUpdateHandler)
|
||||||
http.HandleFunc("/session/create", sessionCreate)
|
http.HandleFunc("/session/create", sessionCreate)
|
||||||
http.HandleFunc("/session/retrieve", sessionRetrieve)
|
http.HandleFunc("/session/retrieve", sessionRetrieve)
|
||||||
http.HandleFunc("/session/authenticate", sessionAuthenticate)
|
http.HandleFunc("/session/authenticate", sessionAuthenticate)
|
||||||
http.HandleFunc("/user/password", userPassword)
|
http.HandleFunc("/node/tree", nodeTree)
|
||||||
http.HandleFunc("/node/tree", nodeTree)
|
http.HandleFunc("/node/retrieve", nodeRetrieve)
|
||||||
http.HandleFunc("/node/retrieve", nodeRetrieve)
|
http.HandleFunc("/node/create", nodeCreate)
|
||||||
http.HandleFunc("/node/create", nodeCreate)
|
http.HandleFunc("/node/update", nodeUpdate)
|
||||||
http.HandleFunc("/node/update", nodeUpdate)
|
http.HandleFunc("/node/rename", nodeRename)
|
||||||
http.HandleFunc("/node/rename", nodeRename)
|
http.HandleFunc("/node/delete", nodeDelete)
|
||||||
http.HandleFunc("/node/delete", nodeDelete)
|
http.HandleFunc("/node/upload", nodeUpload)
|
||||||
http.HandleFunc("/node/upload", nodeUpload)
|
http.HandleFunc("/node/download", nodeDownload)
|
||||||
http.HandleFunc("/node/download", nodeDownload)
|
http.HandleFunc("/node/search", nodeSearch)
|
||||||
http.HandleFunc("/node/search", nodeSearch)
|
http.HandleFunc("/key/retrieve", keyRetrieve)
|
||||||
http.HandleFunc("/key/retrieve", keyRetrieve)
|
http.HandleFunc("/key/create", keyCreate)
|
||||||
http.HandleFunc("/key/create", keyCreate)
|
http.HandleFunc("/key/counter", keyCounter)
|
||||||
http.HandleFunc("/key/counter", keyCounter)
|
|
||||||
http.HandleFunc("/ws", websocketHandler)
|
http.HandleFunc("/ws", websocketHandler)
|
||||||
http.HandleFunc("/", staticHandler)
|
http.HandleFunc("/", staticHandler)
|
||||||
|
|
||||||
@ -202,37 +201,6 @@ 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) {// {{{
|
func nodeTree(w http.ResponseWriter, r *http.Request) {// {{{
|
||||||
var err error
|
var err error
|
||||||
var session Session
|
var session Session
|
||||||
|
@ -91,7 +91,7 @@ func (session *Session) Authenticate(username, password string) (authenticated b
|
|||||||
FROM public.user
|
FROM public.user
|
||||||
WHERE
|
WHERE
|
||||||
username=$1 AND
|
username=$1 AND
|
||||||
password=password_hash(SUBSTRING(password FROM 1 FOR 32), $2::bytea)
|
password=$2
|
||||||
`,
|
`,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
34
sql/0013.sql
34
sql/0013.sql
@ -1,34 +0,0 @@
|
|||||||
/* 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;
|
|
@ -55,13 +55,6 @@ button {
|
|||||||
border: 2px solid #000;
|
border: 2px solid #000;
|
||||||
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.5);
|
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.5);
|
||||||
z-index: 1025;
|
z-index: 1025;
|
||||||
width: min-content;
|
|
||||||
}
|
|
||||||
#menu .section {
|
|
||||||
padding: 16px 16px 0px 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: #c84a37;
|
|
||||||
}
|
}
|
||||||
#menu .item {
|
#menu .item {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@ -144,7 +137,7 @@ button {
|
|||||||
header {
|
header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
grid-template-columns: min-content 1fr repeat(4, min-content);
|
grid-template-columns: min-content 1fr repeat(3, min-content);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 0px;
|
padding: 8px 0px;
|
||||||
color: #333c11;
|
color: #333c11;
|
||||||
@ -168,13 +161,17 @@ header .name {
|
|||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
header .search,
|
header .add {
|
||||||
header .add,
|
padding-right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
header .add img {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
header .keys {
|
header .keys {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
header .search img,
|
|
||||||
header .add img,
|
|
||||||
header .keys img {
|
header .keys img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@ -279,11 +276,6 @@ header .menu {
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
#notes-version {
|
|
||||||
margin-top: 64px;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#node-content.encrypted {
|
#node-content.encrypted {
|
||||||
color: #a00;
|
color: #a00;
|
||||||
}
|
}
|
||||||
@ -546,19 +538,6 @@ header .menu {
|
|||||||
#app.node.toggle-tree #tree {
|
#app.node.toggle-tree #tree {
|
||||||
display: none;
|
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) {
|
@media only screen and (max-width: 932px) {
|
||||||
#app.node {
|
#app.node {
|
||||||
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank";
|
grid-template-areas: "header" "crumbs" "child-nodes" "name" "content" "files" "blank";
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="191.20831"
|
|
||||||
height="227.46098"
|
|
||||||
viewBox="0 0 50.590532 60.182387"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
|
|
||||||
sodipodi:docname="search.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
|
||||||
id="defs2">
|
|
||||||
<linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
id="linearGradient16049">
|
|
||||||
<stop
|
|
||||||
style="stop-color:#ffffff;stop-opacity:0.36503741;"
|
|
||||||
offset="0"
|
|
||||||
id="stop16045" />
|
|
||||||
<stop
|
|
||||||
style="stop-color:#ffffff;stop-opacity:0;"
|
|
||||||
offset="1"
|
|
||||||
id="stop16047" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient
|
|
||||||
inkscape:collect="always"
|
|
||||||
xlink:href="#linearGradient16049"
|
|
||||||
id="linearGradient16051"
|
|
||||||
x1="36.007183"
|
|
||||||
y1="8.6465521"
|
|
||||||
x2="12.402377"
|
|
||||||
y2="41.804993"
|
|
||||||
gradientUnits="userSpaceOnUse" />
|
|
||||||
</defs>
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#5056d3"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.8284271"
|
|
||||||
inkscape:cx="153.08862"
|
|
||||||
inkscape:cy="145.664"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="2190"
|
|
||||||
inkscape:window-height="1404"
|
|
||||||
inkscape:window-x="1463"
|
|
||||||
inkscape:window-y="16"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:showpageshadow="true"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d6d6d6"
|
|
||||||
showborder="true"
|
|
||||||
showguides="false">
|
|
||||||
<sodipodi:guide
|
|
||||||
position="57.608417,-11.948636"
|
|
||||||
orientation="0.81915204,0.57357644"
|
|
||||||
id="guide5717"
|
|
||||||
inkscape:locked="false"
|
|
||||||
inkscape:label=""
|
|
||||||
inkscape:color="rgb(0,134,229)" />
|
|
||||||
</sodipodi:namedview>
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-2.3771159,-1.8407145)">
|
|
||||||
<path
|
|
||||||
style="color:#000000;overflow:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.46667;stroke-linecap:round;stroke-dasharray:none;paint-order:markers stroke fill;stop-color:#000000"
|
|
||||||
d="m 38.808695,43.970762 9.925619,13.819002"
|
|
||||||
id="path2090" />
|
|
||||||
<path
|
|
||||||
id="circle4774"
|
|
||||||
style="color:#000000;overflow:visible;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.175;paint-order:markers stroke fill;stop-color:#000000"
|
|
||||||
d="M 25.660449,1.8407145 A 23.283335,23.283335 0 0 0 2.3771159,25.124048 23.283335,23.283335 0 0 0 25.660449,48.407381 23.283335,23.283335 0 0 0 48.944299,25.124048 23.283335,23.283335 0 0 0 25.660449,1.8407145 Z m 0,6.35 A 16.933332,16.933332 0 0 1 42.593783,25.124048 16.933332,16.933332 0 0 1 25.660449,42.057381 16.933332,16.933332 0 0 1 8.7276327,25.124048 16.933332,16.933332 0 0 1 25.660449,8.1907145 Z" />
|
|
||||||
<circle
|
|
||||||
style="color:#000000;overflow:visible;fill:url(#linearGradient16051);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.175;paint-order:markers stroke fill;stop-color:#000000"
|
|
||||||
id="path429"
|
|
||||||
cx="25.660707"
|
|
||||||
cy="25.123877"
|
|
||||||
r="16.933332" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.8 KiB |
@ -57,7 +57,7 @@ export class NodeUI extends Component {
|
|||||||
case 'node':
|
case 'node':
|
||||||
if(node.ID == 0) {
|
if(node.ID == 0) {
|
||||||
page = html`
|
page = html`
|
||||||
${children.length > 0 ? html`<div class="child-nodes">${children}</div><div id="notes-version">Notes version ${window._VERSION}</div>` : html``}
|
${children.length > 0 ? html`<div class="child-nodes">${children}</div>` : html``}
|
||||||
`
|
`
|
||||||
} else {
|
} else {
|
||||||
let padlock = ''
|
let padlock = ''
|
||||||
@ -86,10 +86,6 @@ export class NodeUI extends Component {
|
|||||||
page = html`<${Keys} nodeui=${this} />`
|
page = html`<${Keys} nodeui=${this} />`
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'profile-settings':
|
|
||||||
page = html`<${ProfileSettings} nodeui=${this} />`
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'search':
|
case 'search':
|
||||||
page = html`<${Search} nodeui=${this} />`
|
page = html`<${Search} nodeui=${this} />`
|
||||||
break
|
break
|
||||||
@ -104,10 +100,9 @@ export class NodeUI extends Component {
|
|||||||
<header class="${modified}" onclick=${()=>this.saveNode()}>
|
<header class="${modified}" onclick=${()=>this.saveNode()}>
|
||||||
<div class="tree"><img src="/images/${window._VERSION}/tree.svg" onclick=${()=>document.getElementById('app').classList.toggle('toggle-tree')} /></div>
|
<div class="tree"><img src="/images/${window._VERSION}/tree.svg" onclick=${()=>document.getElementById('app').classList.toggle('toggle-tree')} /></div>
|
||||||
<div class="name">Notes</div>
|
<div class="name">Notes</div>
|
||||||
<div class="search" onclick=${evt=>{ evt.stopPropagation(); this.showPage('search')}}><img src="/images/${window._VERSION}/search.svg" /></div>
|
<div class="add" onclick=${evt=>this.createNode(evt)}><img src="/images/${window._VERSION}/add.svg" /></div>
|
||||||
<div class="add" onclick=${evt=>this.createNode(evt)}><img src="/images/${window._VERSION}/add.svg" /></div>
|
<div class="keys" onclick=${evt=>{ evt.stopPropagation(); this.showPage('keys')}}><img src="/images/${window._VERSION}/padlock.svg" /></div>
|
||||||
<div class="keys" onclick=${evt=>{ evt.stopPropagation(); this.showPage('keys')}}><img src="/images/${window._VERSION}/padlock.svg" /></div>
|
<div class="menu" onclick=${evt=>this.showMenu(evt)}>☰</div>
|
||||||
<div class="menu" onclick=${evt=>this.showMenu(evt)}>☰</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="crumbs">
|
<div id="crumbs">
|
||||||
@ -218,11 +213,7 @@ export class NodeUI extends Component {
|
|||||||
this.node.value.create(name, nodeID=>this.goToNode(nodeID))
|
this.node.value.create(name, nodeID=>this.goToNode(nodeID))
|
||||||
}//}}}
|
}//}}}
|
||||||
saveNode() {//{{{
|
saveNode() {//{{{
|
||||||
let nodeContent = this.nodeContent.current
|
let content = this.nodeContent.current.contentDiv.current.value
|
||||||
if(this.page.value != 'node' || nodeContent === null)
|
|
||||||
return
|
|
||||||
|
|
||||||
let content = nodeContent.contentDiv.current.value
|
|
||||||
this.node.value.setContent(content)
|
this.node.value.setContent(content)
|
||||||
this.node.value.save(()=>this.props.app.nodeModified.value = false)
|
this.node.value.save(()=>this.props.app.nodeModified.value = false)
|
||||||
}//}}}
|
}//}}}
|
||||||
@ -556,14 +547,10 @@ class Menu extends Component {
|
|||||||
return html`
|
return html`
|
||||||
<div id="blackout" onclick=${()=>nodeui.menu.value = false}></div>
|
<div id="blackout" onclick=${()=>nodeui.menu.value = false}></div>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<div class="section">Current note</div>
|
|
||||||
<div class="item" onclick=${()=>{ nodeui.renameNode(); nodeui.menu.value = false }}>Rename</div>
|
<div class="item" onclick=${()=>{ nodeui.renameNode(); nodeui.menu.value = false }}>Rename</div>
|
||||||
<div class="item" onclick=${()=>{ nodeui.upload.value = true; nodeui.menu.value = false }}>Upload</div>
|
<div class="item" onclick=${()=>{ nodeui.deleteNode(); nodeui.menu.value = false }}>Delete</div>
|
||||||
<div class="item " onclick=${()=>{ nodeui.showPage('node-properties'); nodeui.menu.value = false }}>Properties</div>
|
<div class="item separator" onclick=${()=>{ nodeui.showPage('properties'); nodeui.menu.value = false }}>Properties</div>
|
||||||
<div class="item separator" onclick=${()=>{ nodeui.deleteNode(); nodeui.menu.value = false }}>Delete</div>
|
<div class="item separator" onclick=${()=>{ nodeui.upload.value = true; nodeui.menu.value = false }}>Upload</div>
|
||||||
|
|
||||||
<div class="section">User</div>
|
|
||||||
<div class="item" onclick=${()=>{ nodeui.showPage('profile-settings'); nodeui.menu.value = false }}>Settings</div>
|
|
||||||
<div class="item" onclick=${()=>{ nodeui.logout(); nodeui.menu.value = false }}>Log out</div>
|
<div class="item" onclick=${()=>{ nodeui.logout(); nodeui.menu.value = false }}>Log out</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -801,77 +788,4 @@ class Search extends Component {
|
|||||||
}//}}}
|
}//}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileSettings extends Component {
|
|
||||||
render({ nodeui }, {}) {//{{{
|
|
||||||
return html`
|
|
||||||
<div id="profile-settings">
|
|
||||||
<h1>User settings</h1>
|
|
||||||
|
|
||||||
<h2>Password</h2>
|
|
||||||
<div class="passwords">
|
|
||||||
<div>Current</div>
|
|
||||||
<input type="password" id="current-password" placeholder="Current password" onkeydown=${evt=>this.keyHandler(evt)} />
|
|
||||||
|
|
||||||
<div>New</div>
|
|
||||||
<input type="password" id="new-password1" placeholder="Password" onkeydown=${evt=>this.keyHandler(evt)} />
|
|
||||||
|
|
||||||
<div>Repeat</div>
|
|
||||||
<input type="password" id="new-password2" placeholder="Repeat password" onkeydown=${evt=>this.keyHandler(evt)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick=${()=>this.updatePassword()}>Change password</button>
|
|
||||||
</div>`
|
|
||||||
}//}}}
|
|
||||||
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
|
// vim: foldmethod=marker
|
||||||
|
@ -56,14 +56,6 @@ button {
|
|||||||
border: 2px solid #000;
|
border: 2px solid #000;
|
||||||
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.5);
|
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.5);
|
||||||
z-index: 1025;
|
z-index: 1025;
|
||||||
width: min-content;
|
|
||||||
|
|
||||||
.section {
|
|
||||||
padding: 16px 16px 0px 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: @accent_3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@ -162,7 +154,7 @@ button {
|
|||||||
header {
|
header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
grid-template-columns: min-content 1fr repeat(4, min-content);
|
grid-template-columns: min-content 1fr repeat(3, min-content);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px 0px;
|
padding: 8px 0px;
|
||||||
color: darken(@accent_1, 35%);
|
color: darken(@accent_1, 35%);
|
||||||
@ -189,7 +181,17 @@ header {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search, .add, .keys {
|
.add {
|
||||||
|
padding-right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keys {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@ -322,12 +324,6 @@ header {
|
|||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#notes-version {
|
|
||||||
margin-top: 64px;
|
|
||||||
color: #888;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#node-content.encrypted {
|
#node-content.encrypted {
|
||||||
color: #a00;
|
color: #a00;
|
||||||
}
|
}
|
||||||
@ -621,22 +617,6 @@ 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) {
|
@media only screen and (max-width: 932px) {
|
||||||
#app.node {
|
#app.node {
|
||||||
.layout-crumbs();
|
.layout-crumbs();
|
||||||
|
37
user.go
37
user.go
@ -1,37 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user