added 'remember me'
This commit is contained in:
parent
61fcf2dcfe
commit
ab454f4b99
9 changed files with 63 additions and 35 deletions
|
|
@ -33,7 +33,7 @@ func Render(p *core.Page) string {
|
||||||
toc, html := buildTOC(string(rend))
|
toc, html := buildTOC(string(rend))
|
||||||
html = strings.Replace(html, "<table>", `<table class="table">`, -1)
|
html = strings.Replace(html, "<table>", `<table class="table">`, -1)
|
||||||
html = strings.Replace(html, "</h1>", `</h1><hr>`, -1)
|
html = strings.Replace(html, "</h1>", `</h1><hr>`, -1)
|
||||||
html = strings.Replace(html, "<a", `<a target="_blank"`, -1)
|
html = strings.Replace(html, "<a", `<a target="_blank" rel="noopener noreferrer"`, -1)
|
||||||
p.TOC = template.HTML(toc)
|
p.TOC = template.HTML(toc)
|
||||||
p.HTML = template.HTML(html)
|
p.HTML = template.HTML(html)
|
||||||
return whiteSpace.ReplaceAllString(string(filter.Filter(rend)), " ")
|
return whiteSpace.ReplaceAllString(string(filter.Filter(rend)), " ")
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const (
|
||||||
sectionClaim = "section"
|
sectionClaim = "section"
|
||||||
titleClaim = "title"
|
titleClaim = "title"
|
||||||
totpClaim = "totp"
|
totpClaim = "totp"
|
||||||
|
rememberClaim = "remember"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Context) {
|
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Context) {
|
||||||
|
|
@ -67,7 +68,7 @@ func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Con
|
||||||
log.Println("Invalidate:", err)
|
log.Println("Invalidate:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.SetCookie(nil)
|
ctx.SetCookie(nil, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,14 +231,26 @@ func (c *Context) LoggedOn() (ok bool) {
|
||||||
|
|
||||||
func (c *Context) LogSetCookie(msg string, err error) {
|
func (c *Context) LogSetCookie(msg string, err error) {
|
||||||
log.Printf("ctx: %s %s\n", msg, err)
|
log.Printf("ctx: %s %s\n", msg, err)
|
||||||
c.SetCookie(nil)
|
c.SetCookie(nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) SetCookie(claims map[string]interface{}) {
|
func (c *Context) Login(u core.User, remember string) {
|
||||||
c.SetCookieToken(jwt.NewToken(claims, nil))
|
var d time.Duration
|
||||||
|
if remember != "" {
|
||||||
|
d = day * 7
|
||||||
|
}
|
||||||
|
c.SetCookie(jwt.Claims{userClaim: u.Username, createdClaim: u.Created, adminClaim: u.Admin}, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) SetCookieToken(t *jwt.Token) {
|
func (c *Context) SetCookie(claims jwt.Claims, d time.Duration) {
|
||||||
|
c.setCookie(jwt.NewToken(claims, nil), d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) setCookie(t *jwt.Token, d time.Duration) {
|
||||||
|
if d == 0 {
|
||||||
|
d = jwt.DefaultExpiry
|
||||||
|
}
|
||||||
|
t.Claims[jwt.ExpClaim] = jwt.NewExp(d)
|
||||||
if err := c.Srv.JWT.Sign(t); err != nil {
|
if err := c.Srv.JWT.Sign(t); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
|
@ -247,13 +260,12 @@ func (c *Context) SetCookieToken(t *jwt.Token) {
|
||||||
Name: cookieName,
|
Name: cookieName,
|
||||||
Value: c.Token.String(),
|
Value: c.Token.String(),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: int(jwt.DefaultExpiry.Seconds()),
|
MaxAge: int(d.Seconds()),
|
||||||
Secure: c.Srv.Config.SecureCookie,
|
Secure: c.Srv.Config.SecureCookie,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
SameSite: http.SameSiteStrictMode,
|
SameSite: http.SameSiteStrictMode,
|
||||||
}
|
}
|
||||||
if cookie.MaxAge > 0 {
|
if cookie.MaxAge > 0 {
|
||||||
d := time.Duration(cookie.MaxAge) * time.Second
|
|
||||||
cookie.Expires = time.Now().Add(d)
|
cookie.Expires = time.Now().Add(d)
|
||||||
} else if cookie.MaxAge < 0 {
|
} else if cookie.MaxAge < 0 {
|
||||||
cookie.Expires = time.Unix(1, 0)
|
cookie.Expires = time.Unix(1, 0)
|
||||||
|
|
@ -269,6 +281,10 @@ func (c *Context) Totp() string {
|
||||||
return c.Token.Claims.GetString(totpClaim)
|
return c.Token.Claims.GetString(totpClaim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) Remember() string {
|
||||||
|
return c.Token.Claims.GetString(rememberClaim)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) Admin() bool {
|
func (c *Context) Admin() bool {
|
||||||
return c.Token.Claims.GetBool(adminClaim)
|
return c.Token.Claims.GetBool(adminClaim)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ func authHandler(h ctxHandler) ctxHandler {
|
||||||
func staticHandler(ctx *Context) {
|
func staticHandler(ctx *Context) {
|
||||||
var err error
|
var err error
|
||||||
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
|
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
|
||||||
ctx.SetHeader("Expires", time.Now().Add(time.Hour*12).Format(http.TimeFormat))
|
ctx.SetHeader("Expires", time.Now().UTC().Add(max).Format(http.TimeFormat))
|
||||||
switch ctx.Path() {
|
switch ctx.Path() {
|
||||||
case "/bootstrap.css":
|
case "/bootstrap.css":
|
||||||
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
|
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
|
||||||
|
|
@ -116,14 +116,14 @@ func loginHandler(ctx *Context) {
|
||||||
}
|
}
|
||||||
if u.Secret == "" {
|
if u.Secret == "" {
|
||||||
log.Println("login:", u.Username)
|
log.Println("login:", u.Username)
|
||||||
ctx.SetCookie(jwt.Claims{userClaim: u.Username, createdClaim: u.Created, adminClaim: u.Admin})
|
ctx.Login(u, ctx.Form("remember"))
|
||||||
ctx.Redirect(core.IndexURI, http.StatusFound)
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.SetCookieToken(jwt.NewToken(map[string]interface{}{
|
ctx.SetCookie(jwt.Claims{
|
||||||
totpClaim: u.Username,
|
totpClaim: u.Username,
|
||||||
jwt.ExpClaim: jwt.NewExp(time.Minute),
|
rememberClaim: ctx.Form("remember"),
|
||||||
}, nil))
|
}, time.Minute)
|
||||||
ctx.Redirect(core.TotpURI, http.StatusFound)
|
ctx.Redirect(core.TotpURI, http.StatusFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ func loginTotpHandler(ctx *Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("login:", u.Username)
|
log.Println("login:", u.Username)
|
||||||
ctx.SetCookie(jwt.Claims{userClaim: u.Username, createdClaim: u.Created, adminClaim: u.Admin})
|
ctx.Login(u, ctx.Remember())
|
||||||
ctx.Redirect(core.IndexURI, http.StatusFound)
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -676,6 +676,6 @@ func userDelHandler(ctx *Context) {
|
||||||
func logoutHandler(ctx *Context) {
|
func logoutHandler(ctx *Context) {
|
||||||
log.Println("logout:", ctx.User())
|
log.Println("logout:", ctx.User())
|
||||||
ctx.Srv.JWT.Invalidate(&ctx.Token)
|
ctx.Srv.JWT.Invalidate(&ctx.Token)
|
||||||
ctx.SetCookie(nil)
|
ctx.SetCookie(nil, 0)
|
||||||
ctx.Redirect(core.IndexURI, http.StatusFound)
|
ctx.Redirect(core.IndexURI, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ const (
|
||||||
</div>
|
</div>
|
||||||
{{end}}`
|
{{end}}`
|
||||||
index = `<!DOCTYPE html>
|
index = `<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>GoWiki | {{.Title}}</title>
|
<title>GoWiki | {{.Title}}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
||||||
|
|
@ -120,7 +120,13 @@ const (
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="password">Password</label>
|
<label class="col-form-label" for="password">Password</label>
|
||||||
<input class="form-control input-sm" type="password" id="password" name="password" autofocus required>
|
<input class="form-control input-sm" type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="remember" name="remember">
|
||||||
|
<label class="custom-control-label" for="remember">Remember Me</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm btn-primary" type="submit">Login</button>
|
<button class="btn btn-sm btn-primary" type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -339,7 +345,7 @@ const (
|
||||||
<input type="hidden" name="token" value="{{.Token}}">
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="section">Section</label>
|
<label class="col-form-label" for="section">Section</label>
|
||||||
<select class="form-control input-sm" id="section" name="section" autofocus required>
|
<select class="form-control input-sm" id="section" name="section" required>
|
||||||
<option>wiki</option>
|
<option>wiki</option>
|
||||||
<option>{{.User}}</option>
|
<option>{{.User}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -528,7 +534,7 @@ const (
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="repeat">Repeat</label>
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
<input class="form-control input-sm" type="password" id="repeat" name="repeat" autofocus>
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat">
|
||||||
</div>
|
</div>
|
||||||
{{if .Admin}}
|
{{if .Admin}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -635,11 +641,11 @@ const (
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="password">Password</label>
|
<label class="col-form-label" for="password">Password</label>
|
||||||
<input class="form-control input-sm" type="password" id="password" name="password" autofocus required>
|
<input class="form-control input-sm" type="password" id="password" name="password" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="repeat">Repeat</label>
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
<input class="form-control input-sm" type="password" id="repeat" name="repeat" autofocus required>
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="admin">Admin Privileges</label>
|
<label class="col-form-label" for="admin">Admin Privileges</label>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>GoWiki | {{.Title}}</title>
|
<title>GoWiki | {{.Title}}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="password">Password</label>
|
<label class="col-form-label" for="password">Password</label>
|
||||||
<input class="form-control input-sm" type="password" id="password" name="password" autofocus required>
|
<input class="form-control input-sm" type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="remember" name="remember">
|
||||||
|
<label class="custom-control-label" for="remember">Remember Me</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm btn-primary" type="submit">Login</button>
|
<button class="btn btn-sm btn-primary" type="submit">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<input type="hidden" name="token" value="{{.Token}}">
|
<input type="hidden" name="token" value="{{.Token}}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="section">Section</label>
|
<label class="col-form-label" for="section">Section</label>
|
||||||
<select class="form-control input-sm" id="section" name="section" autofocus required>
|
<select class="form-control input-sm" id="section" name="section" required>
|
||||||
<option>wiki</option>
|
<option>wiki</option>
|
||||||
<option>{{.User}}</option>
|
<option>{{.User}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="repeat">Repeat</label>
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
<input class="form-control input-sm" type="password" id="repeat" name="repeat" autofocus>
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat">
|
||||||
</div>
|
</div>
|
||||||
{{if .Admin}}
|
{{if .Admin}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="password">Password</label>
|
<label class="col-form-label" for="password">Password</label>
|
||||||
<input class="form-control input-sm" type="password" id="password" name="password" autofocus required>
|
<input class="form-control input-sm" type="password" id="password" name="password" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="repeat">Repeat</label>
|
<label class="col-form-label" for="repeat">Repeat</label>
|
||||||
<input class="form-control input-sm" type="password" id="repeat" name="repeat" autofocus required>
|
<input class="form-control input-sm" type="password" id="repeat" name="repeat" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-form-label" for="admin">Admin Privileges</label>
|
<label class="col-form-label" for="admin">Admin Privileges</label>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue