diff --git a/handlers.go b/handlers.go
index fa55180..acca91f 100755
--- a/handlers.go
+++ b/handlers.go
@@ -22,6 +22,8 @@ import (
"github.com/schollz/cowyo/encrypt"
)
+const minutesToUnlock = 10.0
+
var customCSS []byte
var defaultLock string
var debounceTime int
@@ -169,6 +171,13 @@ func loadTemplates(list ...string) multitemplate.Render {
return r
}
+func pageIsLocked(p *Page, c *gin.Context) bool {
+ // it is easier to reason about when the page is actually unlocked
+ var unlocked = !p.IsLocked ||
+ (p.IsLocked && p.UnlockedFor == getSetSessionID(c))
+ return !unlocked
+}
+
func handlePageRelinquish(c *gin.Context) {
type QueryJSON struct {
Page string `json:"page"`
@@ -191,10 +200,10 @@ func handlePageRelinquish(c *gin.Context) {
name = json.Page
}
text := p.Text.GetCurrent()
- isLocked := p.IsEncrypted
+ isLocked := pageIsLocked(p, c)
isEncrypted := p.IsEncrypted
destroyed := p.IsPrimedForSelfDestruct
- if !p.IsLocked && p.IsPrimedForSelfDestruct {
+ if !isLocked && p.IsPrimedForSelfDestruct {
p.Erase()
message = "Relinquished and erased"
}
@@ -207,6 +216,22 @@ func handlePageRelinquish(c *gin.Context) {
"destroyed": destroyed})
}
+func getSetSessionID(c *gin.Context) (sid string) {
+ var (
+ session = sessions.Default(c)
+ v = session.Get("sid")
+ )
+ if v != nil {
+ sid = v.(string)
+ }
+ if v == nil || sid == "" {
+ sid = RandStringBytesMaskImprSrc(8)
+ session.Set("sid", sid)
+ session.Save()
+ }
+ return sid
+}
+
func thread_SiteMap() {
for {
log.Info("Generating sitemap...")
@@ -307,23 +332,24 @@ func handlePageRequest(c *gin.Context) {
return
}
- version := c.DefaultQuery("version", "ajksldfjl")
-
// use the default lock
if defaultLock != "" && p.IsNew() {
p.IsLocked = true
p.PassphraseToUnlock = defaultLock
}
+ version := c.DefaultQuery("version", "ajksldfjl")
+ isLocked := pageIsLocked(p, c)
+
// Disallow anything but viewing locked/encrypted pages
- if (p.IsEncrypted || p.IsLocked) &&
+ if (p.IsEncrypted || isLocked) &&
(command[0:2] != "/v" && command[0:2] != "/r") {
c.Redirect(302, "/"+page+"/view")
return
}
// Destroy page if it is opened and primed
- if p.IsPrimedForSelfDestruct && !p.IsLocked && !p.IsEncrypted {
+ if p.IsPrimedForSelfDestruct && !isLocked && !p.IsEncrypted {
p.Update("
This page has self-destructed. You cannot return to it.\n\n" + p.Text.GetCurrent())
p.Erase()
if p.IsPublished {
@@ -333,7 +359,7 @@ func handlePageRequest(c *gin.Context) {
}
}
if command == "/erase" {
- if !p.IsLocked && !p.IsEncrypted {
+ if !isLocked && !p.IsEncrypted {
p.Erase()
c.Redirect(302, "/"+page+"/edit")
} else {
@@ -410,7 +436,7 @@ func handlePageRequest(c *gin.Context) {
"Versions": versionsInt64,
"VersionsText": versionsText,
"VersionsChangeSums": versionsChangeSums,
- "IsLocked": p.IsLocked,
+ "IsLocked": isLocked,
"IsEncrypted": p.IsEncrypted,
"ListItems": renderList(rawText),
"Route": "/" + page + command,
@@ -506,10 +532,19 @@ func handlePageUpdate(c *gin.Context) {
}
log.Trace("Update: %v", json)
p := Open(json.Page)
- var message string
+ var (
+ message string
+ sinceLastEdit = time.Since(p.LastEditTime())
+ )
success := false
- if p.IsLocked {
- message = "Locked, must unlock first"
+ if pageIsLocked(p, c) {
+ if sinceLastEdit < minutesToUnlock {
+ message = "This page is being edited by someone else"
+ } else {
+ // here what might have happened is that two people unlock without
+ // editing thus they both suceeds but only one is able to edit
+ message = "Locked, must unlock first"
+ }
} else if p.IsEncrypted {
message = "Encrypted, must decrypt first"
} else if json.FetchedAt > 0 && p.LastEditUnixTime() > json.FetchedAt {
@@ -541,7 +576,7 @@ func handlePrime(c *gin.Context) {
}
log.Trace("Update: %v", json)
p := Open(json.Page)
- if p.IsLocked {
+ if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
} else if p.IsEncrypted {
@@ -566,7 +601,7 @@ func handleLock(c *gin.Context) {
}
p := Open(json.Page)
if defaultLock != "" && p.IsNew() {
- p.IsLocked = true
+ p.IsLocked = true // IsLocked was replaced by variable wrt Context
p.PassphraseToUnlock = defaultLock
}
@@ -574,21 +609,38 @@ func handleLock(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return
}
- var message string
- if p.IsLocked {
+ var (
+ message string
+ sessionID = getSetSessionID(c)
+ sinceLastEdit = time.Since(p.LastEditTime())
+ )
+
+ // both lock/unlock ends here on locked&timeout combination
+ if p.IsLocked &&
+ p.UnlockedFor != sessionID &&
+ p.UnlockedFor != "" &&
+ sinceLastEdit.Minutes() < minutesToUnlock {
+
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": fmt.Sprintf("This page is being edited by someone else! Will unlock automatically %2.0f minutes after the last change.", minutesToUnlock-sinceLastEdit.Minutes()),
+ })
+ return
+ }
+ if !pageIsLocked(p, c) {
+ p.IsLocked = true
+ p.PassphraseToUnlock = HashPassword(json.Passphrase)
+ p.UnlockedFor = ""
+ message = "Locked"
+ } else {
err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
if err2 != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
return
}
- p.IsLocked = false
- message = "Unlocked"
- } else {
- p.IsLocked = true
- p.PassphraseToUnlock = HashPassword(json.Passphrase)
- message = "Locked"
+ p.UnlockedFor = sessionID
+ message = "Unlocked only for you"
}
- fmt.Println(p)
p.Save()
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
}
@@ -665,7 +717,7 @@ func handleEncrypt(c *gin.Context) {
return
}
p := Open(json.Page)
- if p.IsLocked {
+ if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
}
@@ -749,7 +801,7 @@ func handleClearOldListItems(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
return
}
- if p.IsLocked {
+ if pageIsLocked(p, c) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
return
}
diff --git a/page.go b/page.go
index a766124..0300c44 100755
--- a/page.go
+++ b/page.go
@@ -26,6 +26,7 @@ type Page struct {
IsEncrypted bool
IsPrimedForSelfDestruct bool
IsPublished bool
+ UnlockedFor string
}
func (p Page) LastEditTime() time.Time {