mirror of
https://github.com/miniflux/v2.git
synced 2025-07-02 16:38:37 +00:00
Session management refactoring
This commit is contained in:
parent
58acd1d5e3
commit
00257988ef
26 changed files with 465 additions and 276 deletions
40
server/cookie/cookie.go
Normal file
40
server/cookie/cookie.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cookie
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cookie names.
|
||||
const (
|
||||
CookieSessionID = "sessionID"
|
||||
CookieUserSessionID = "userSessionID"
|
||||
)
|
||||
|
||||
// New create a new cookie.
|
||||
func New(name, value string, isHTTPS bool) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
Secure: isHTTPS,
|
||||
HttpOnly: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Expired returns an expired cookie.
|
||||
func Expired(name string, isHTTPS bool) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: name,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Secure: isHTTPS,
|
||||
HttpOnly: true,
|
||||
MaxAge: -1,
|
||||
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ package core
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/miniflux/miniflux/helper"
|
||||
"github.com/miniflux/miniflux/locale"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/server/middleware"
|
||||
|
@ -18,11 +20,12 @@ import (
|
|||
|
||||
// Context contains helper functions related to the current request.
|
||||
type Context struct {
|
||||
writer http.ResponseWriter
|
||||
request *http.Request
|
||||
store *storage.Storage
|
||||
router *mux.Router
|
||||
user *model.User
|
||||
writer http.ResponseWriter
|
||||
request *http.Request
|
||||
store *storage.Storage
|
||||
router *mux.Router
|
||||
user *model.User
|
||||
translator *locale.Translator
|
||||
}
|
||||
|
||||
// IsAdminUser checks if the logged user is administrator.
|
||||
|
@ -35,10 +38,11 @@ func (c *Context) IsAdminUser() bool {
|
|||
|
||||
// UserTimezone returns the timezone used by the logged user.
|
||||
func (c *Context) UserTimezone() string {
|
||||
if v := c.request.Context().Value(middleware.UserTimezoneContextKey); v != nil {
|
||||
return v.(string)
|
||||
value := c.getContextStringValue(middleware.UserTimezoneContextKey)
|
||||
if value == "" {
|
||||
value = "UTC"
|
||||
}
|
||||
return "UTC"
|
||||
return value
|
||||
}
|
||||
|
||||
// IsAuthenticated returns a boolean if the user is authenticated.
|
||||
|
@ -80,13 +84,68 @@ func (c *Context) UserLanguage() string {
|
|||
return user.Language
|
||||
}
|
||||
|
||||
// CsrfToken returns the current CSRF token.
|
||||
func (c *Context) CsrfToken() string {
|
||||
if v := c.request.Context().Value(middleware.TokenContextKey); v != nil {
|
||||
// Translate translates a message in the current language.
|
||||
func (c *Context) Translate(message string, args ...interface{}) string {
|
||||
return c.translator.GetLanguage(c.UserLanguage()).Get(message, args...)
|
||||
}
|
||||
|
||||
// CSRF returns the current CSRF token.
|
||||
func (c *Context) CSRF() string {
|
||||
return c.getContextStringValue(middleware.CSRFContextKey)
|
||||
}
|
||||
|
||||
// SessionID returns the current session ID.
|
||||
func (c *Context) SessionID() string {
|
||||
return c.getContextStringValue(middleware.SessionIDContextKey)
|
||||
}
|
||||
|
||||
// UserSessionToken returns the current user session token.
|
||||
func (c *Context) UserSessionToken() string {
|
||||
return c.getContextStringValue(middleware.UserSessionTokenContextKey)
|
||||
}
|
||||
|
||||
// OAuth2State returns the current OAuth2 state.
|
||||
func (c *Context) OAuth2State() string {
|
||||
return c.getContextStringValue(middleware.OAuth2StateContextKey)
|
||||
}
|
||||
|
||||
// GenerateOAuth2State generate a new OAuth2 state.
|
||||
func (c *Context) GenerateOAuth2State() string {
|
||||
state := helper.GenerateRandomString(32)
|
||||
c.store.UpdateSessionField(c.SessionID(), "oauth2_state", state)
|
||||
return state
|
||||
}
|
||||
|
||||
// SetFlashMessage defines a new flash message.
|
||||
func (c *Context) SetFlashMessage(message string) {
|
||||
c.store.UpdateSessionField(c.SessionID(), "flash_message", message)
|
||||
}
|
||||
|
||||
// FlashMessage returns the flash message and remove it.
|
||||
func (c *Context) FlashMessage() string {
|
||||
message := c.getContextStringValue(middleware.FlashMessageContextKey)
|
||||
c.store.UpdateSessionField(c.SessionID(), "flash_message", "")
|
||||
return message
|
||||
}
|
||||
|
||||
// SetFlashErrorMessage defines a new flash error message.
|
||||
func (c *Context) SetFlashErrorMessage(message string) {
|
||||
c.store.UpdateSessionField(c.SessionID(), "flash_error_message", message)
|
||||
}
|
||||
|
||||
// FlashErrorMessage returns the error flash message and remove it.
|
||||
func (c *Context) FlashErrorMessage() string {
|
||||
message := c.getContextStringValue(middleware.FlashMessageContextKey)
|
||||
c.store.UpdateSessionField(c.SessionID(), "flash_error_message", "")
|
||||
return message
|
||||
}
|
||||
|
||||
func (c *Context) getContextStringValue(key *middleware.ContextKey) string {
|
||||
if v := c.request.Context().Value(key); v != nil {
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
logger.Error("No CSRF token in context!")
|
||||
logger.Error("[Core:Context] Missing key: %s", key)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -96,6 +155,6 @@ func (c *Context) Route(name string, args ...interface{}) string {
|
|||
}
|
||||
|
||||
// NewContext creates a new Context.
|
||||
func NewContext(w http.ResponseWriter, r *http.Request, store *storage.Storage, router *mux.Router) *Context {
|
||||
return &Context{writer: w, request: r, store: store, router: router}
|
||||
func NewContext(w http.ResponseWriter, r *http.Request, store *storage.Storage, router *mux.Router, translator *locale.Translator) *Context {
|
||||
return &Context{writer: w, request: r, store: store, router: router, translator: translator}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func (h *Handler) Use(f HandlerFunc) http.Handler {
|
|||
defer helper.ExecutionTime(time.Now(), r.URL.Path)
|
||||
logger.Debug("[HTTP] %s %s", r.Method, r.URL.Path)
|
||||
|
||||
ctx := NewContext(w, r, h.store, h.router)
|
||||
ctx := NewContext(w, r, h.store, h.router, h.translator)
|
||||
request := NewRequest(w, r)
|
||||
response := NewResponse(w, r, h.template)
|
||||
|
||||
|
|
|
@ -4,23 +4,43 @@
|
|||
|
||||
package middleware
|
||||
|
||||
type contextKey struct {
|
||||
// ContextKey represents a context key.
|
||||
type ContextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (c ContextKey) String() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
var (
|
||||
// UserIDContextKey is the context key used to store the user ID.
|
||||
UserIDContextKey = &contextKey{"UserID"}
|
||||
UserIDContextKey = &ContextKey{"UserID"}
|
||||
|
||||
// UserTimezoneContextKey is the context key used to store the user timezone.
|
||||
UserTimezoneContextKey = &contextKey{"UserTimezone"}
|
||||
UserTimezoneContextKey = &ContextKey{"UserTimezone"}
|
||||
|
||||
// IsAdminUserContextKey is the context key used to store the user role.
|
||||
IsAdminUserContextKey = &contextKey{"IsAdminUser"}
|
||||
IsAdminUserContextKey = &ContextKey{"IsAdminUser"}
|
||||
|
||||
// IsAuthenticatedContextKey is the context key used to store the authentication flag.
|
||||
IsAuthenticatedContextKey = &contextKey{"IsAuthenticated"}
|
||||
IsAuthenticatedContextKey = &ContextKey{"IsAuthenticated"}
|
||||
|
||||
// TokenContextKey is the context key used to store CSRF token.
|
||||
TokenContextKey = &contextKey{"CSRF"}
|
||||
// UserSessionTokenContextKey is the context key used to store the user session ID.
|
||||
UserSessionTokenContextKey = &ContextKey{"UserSessionToken"}
|
||||
|
||||
// SessionIDContextKey is the context key used to store the session ID.
|
||||
SessionIDContextKey = &ContextKey{"SessionID"}
|
||||
|
||||
// CSRFContextKey is the context key used to store CSRF token.
|
||||
CSRFContextKey = &ContextKey{"CSRF"}
|
||||
|
||||
// OAuth2StateContextKey is the context key used to store OAuth2 state.
|
||||
OAuth2StateContextKey = &ContextKey{"OAuth2State"}
|
||||
|
||||
// FlashMessageContextKey is the context key used to store a flash message.
|
||||
FlashMessageContextKey = &ContextKey{"FlashMessage"}
|
||||
|
||||
// FlashErrorMessageContextKey is the context key used to store a flash error message.
|
||||
FlashErrorMessageContextKey = &ContextKey{"FlashErrorMessage"}
|
||||
)
|
||||
|
|
|
@ -10,60 +10,66 @@ import (
|
|||
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/server/route"
|
||||
"github.com/miniflux/miniflux/server/cookie"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// SessionMiddleware represents a session middleware.
|
||||
type SessionMiddleware struct {
|
||||
store *storage.Storage
|
||||
router *mux.Router
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
// Handler execute the middleware.
|
||||
func (s *SessionMiddleware) Handler(next http.Handler) http.Handler {
|
||||
func (t *SessionMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := s.getSessionFromCookie(r)
|
||||
var err error
|
||||
session := t.getSessionValueFromCookie(r)
|
||||
|
||||
if session == nil {
|
||||
logger.Debug("[Middleware:Session] Session not found")
|
||||
if s.isPublicRoute(r) {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.Redirect(w, r, route.Path(s.router, "login"), http.StatusFound)
|
||||
session, err = t.store.CreateSession()
|
||||
if err != nil {
|
||||
logger.Error("[Middleware:Session] %v", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, r.URL.Scheme == "https"))
|
||||
} else {
|
||||
logger.Debug("[Middleware:Session] %s", session)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserIDContextKey, session.UserID)
|
||||
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
formValue := r.FormValue("csrf")
|
||||
headerValue := r.Header.Get("X-Csrf-Token")
|
||||
|
||||
if session.Data.CSRF != formValue && session.Data.CSRF != headerValue {
|
||||
logger.Error(`[Middleware:Session] Invalid or missing CSRF token: Form="%s", Header="%s"`, formValue, headerValue)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Invalid or missing CSRF session!"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, SessionIDContextKey, session.ID)
|
||||
ctx = context.WithValue(ctx, CSRFContextKey, session.Data.CSRF)
|
||||
ctx = context.WithValue(ctx, OAuth2StateContextKey, session.Data.OAuth2State)
|
||||
ctx = context.WithValue(ctx, FlashMessageContextKey, session.Data.FlashMessage)
|
||||
ctx = context.WithValue(ctx, FlashErrorMessageContextKey, session.Data.FlashErrorMessage)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SessionMiddleware) isPublicRoute(r *http.Request) bool {
|
||||
route := mux.CurrentRoute(r)
|
||||
switch route.GetName() {
|
||||
case "login", "checkLogin", "stylesheet", "javascript", "oauth2Redirect", "oauth2Callback", "appIcon", "favicon":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionMiddleware) getSessionFromCookie(r *http.Request) *model.UserSession {
|
||||
sessionCookie, err := r.Cookie("sessionID")
|
||||
func (t *SessionMiddleware) getSessionValueFromCookie(r *http.Request) *model.Session {
|
||||
sessionCookie, err := r.Cookie(cookie.CookieSessionID)
|
||||
if err == http.ErrNoCookie {
|
||||
return nil
|
||||
}
|
||||
|
||||
session, err := s.store.UserSessionByToken(sessionCookie.Value)
|
||||
session, err := t.store.Session(sessionCookie.Value)
|
||||
if err != nil {
|
||||
logger.Error("[SessionMiddleware] %v", err)
|
||||
logger.Error("[Middleware:Session] %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -71,6 +77,6 @@ func (s *SessionMiddleware) getSessionFromCookie(r *http.Request) *model.UserSes
|
|||
}
|
||||
|
||||
// NewSessionMiddleware returns a new SessionMiddleware.
|
||||
func NewSessionMiddleware(s *storage.Storage, r *mux.Router) *SessionMiddleware {
|
||||
return &SessionMiddleware{store: s, router: r}
|
||||
func NewSessionMiddleware(s *storage.Storage) *SessionMiddleware {
|
||||
return &SessionMiddleware{store: s}
|
||||
}
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
)
|
||||
|
||||
// TokenMiddleware represents a token middleware.
|
||||
type TokenMiddleware struct {
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
// Handler execute the middleware.
|
||||
func (t *TokenMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
token := t.getTokenValueFromCookie(r)
|
||||
|
||||
if token == nil {
|
||||
logger.Debug("[Middleware:Token] Token not found")
|
||||
token, err = t.store.CreateToken()
|
||||
if err != nil {
|
||||
logger.Error("[Middleware:Token] %v", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "tokenID",
|
||||
Value: token.ID,
|
||||
Path: "/",
|
||||
Secure: r.URL.Scheme == "https",
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
} else {
|
||||
logger.Info("[Middleware:Token] %s", token)
|
||||
}
|
||||
|
||||
isTokenValid := token.Value == r.FormValue("csrf") || token.Value == r.Header.Get("X-Csrf-Token")
|
||||
|
||||
if r.Method == "POST" && !isTokenValid {
|
||||
logger.Error("[Middleware:CSRF] Invalid or missing CSRF token!")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Invalid or missing CSRF token!"))
|
||||
} else {
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, TokenContextKey, token.Value)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *TokenMiddleware) getTokenValueFromCookie(r *http.Request) *model.Token {
|
||||
tokenCookie, err := r.Cookie("tokenID")
|
||||
if err == http.ErrNoCookie {
|
||||
return nil
|
||||
}
|
||||
|
||||
token, err := t.store.Token(tokenCookie.Value)
|
||||
if err != nil {
|
||||
logger.Error("[Middleware:Token] %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// NewTokenMiddleware returns a new TokenMiddleware.
|
||||
func NewTokenMiddleware(s *storage.Storage) *TokenMiddleware {
|
||||
return &TokenMiddleware{store: s}
|
||||
}
|
78
server/middleware/user_session.go
Normal file
78
server/middleware/user_session.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/server/cookie"
|
||||
"github.com/miniflux/miniflux/server/route"
|
||||
"github.com/miniflux/miniflux/storage"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// UserSessionMiddleware represents a user session middleware.
|
||||
type UserSessionMiddleware struct {
|
||||
store *storage.Storage
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// Handler execute the middleware.
|
||||
func (s *UserSessionMiddleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
session := s.getSessionFromCookie(r)
|
||||
|
||||
if session == nil {
|
||||
logger.Debug("[Middleware:UserSession] Session not found")
|
||||
if s.isPublicRoute(r) {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.Redirect(w, r, route.Path(s.router, "login"), http.StatusFound)
|
||||
}
|
||||
} else {
|
||||
logger.Debug("[Middleware:UserSession] %s", session)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserIDContextKey, session.UserID)
|
||||
ctx = context.WithValue(ctx, IsAuthenticatedContextKey, true)
|
||||
ctx = context.WithValue(ctx, UserSessionTokenContextKey, session.Token)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserSessionMiddleware) isPublicRoute(r *http.Request) bool {
|
||||
route := mux.CurrentRoute(r)
|
||||
switch route.GetName() {
|
||||
case "login", "checkLogin", "stylesheet", "javascript", "oauth2Redirect", "oauth2Callback", "appIcon", "favicon":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserSessionMiddleware) getSessionFromCookie(r *http.Request) *model.UserSession {
|
||||
sessionCookie, err := r.Cookie(cookie.CookieUserSessionID)
|
||||
if err == http.ErrNoCookie {
|
||||
return nil
|
||||
}
|
||||
|
||||
session, err := s.store.UserSessionByToken(sessionCookie.Value)
|
||||
if err != nil {
|
||||
logger.Error("[Middleware:UserSession] %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
// NewUserSessionMiddleware returns a new UserSessionMiddleware.
|
||||
func NewUserSessionMiddleware(s *storage.Storage, r *mux.Router) *UserSessionMiddleware {
|
||||
return &UserSessionMiddleware{store: s, router: r}
|
||||
}
|
|
@ -42,8 +42,8 @@ func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Han
|
|||
))
|
||||
|
||||
uiHandler := core.NewHandler(store, router, templateEngine, translator, middleware.NewChain(
|
||||
middleware.NewSessionMiddleware(store, router).Handler,
|
||||
middleware.NewTokenMiddleware(store).Handler,
|
||||
middleware.NewUserSessionMiddleware(store, router).Handler,
|
||||
middleware.NewSessionMiddleware(store).Handler,
|
||||
))
|
||||
|
||||
router.Handle("/fever/", feverHandler.Use(feverController.Handler))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2017-12-15 21:24:38.377969493 -0800 PST m=+0.007061903
|
||||
// 2017-12-16 17:48:32.321995978 -0800 PST m=+0.055632657
|
||||
|
||||
package template
|
||||
|
||||
|
@ -88,6 +88,12 @@ var templateCommonMap = map[string]string{
|
|||
</nav>
|
||||
</header>
|
||||
{{ end }}
|
||||
{{ if .flashMessage }}
|
||||
<div class="flash-message alert alert-success">{{ .flashMessage }}</div>
|
||||
{{ end }}
|
||||
{{ if .flashErrorMessage }}
|
||||
<div class="flash-error-message alert alert-error">{{ .flashErrorMessage }}</div>
|
||||
{{ end }}
|
||||
<main>
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
|
@ -118,6 +124,6 @@ var templateCommonMap = map[string]string{
|
|||
|
||||
var templateCommonMapChecksums = map[string]string{
|
||||
"entry_pagination": "f1465fa70f585ae8043b200ec9de5bf437ffbb0c19fb7aefc015c3555614ee27",
|
||||
"layout": "100d1ffff506b9cdd4c28233ff883c323452ea01fa224ff891d4ad69997b62b1",
|
||||
"layout": "ff5e3d87a48e4d3aeceda4aabe6c2c2f607006c6b6e83dfcab6c5eb255a1e6f2",
|
||||
"pagination": "6ff462c2b2a53bc5448b651da017f40a39f1d4f16cef4b2f09784f0797286924",
|
||||
}
|
||||
|
|
|
@ -63,6 +63,12 @@
|
|||
</nav>
|
||||
</header>
|
||||
{{ end }}
|
||||
{{ if .flashMessage }}
|
||||
<div class="flash-message alert alert-success">{{ .flashMessage }}</div>
|
||||
{{ end }}
|
||||
{{ if .flashErrorMessage }}
|
||||
<div class="flash-error-message alert alert-error">{{ .flashErrorMessage }}</div>
|
||||
{{ end }}
|
||||
<main>
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
|
|
|
@ -44,10 +44,11 @@ func (c *Controller) getCommonTemplateArgs(ctx *core.Context) (tplParams, error)
|
|||
}
|
||||
|
||||
params := tplParams{
|
||||
"menu": "",
|
||||
"user": user,
|
||||
"countUnread": countUnread,
|
||||
"csrf": ctx.CsrfToken(),
|
||||
"menu": "",
|
||||
"user": user,
|
||||
"countUnread": countUnread,
|
||||
"csrf": ctx.CSRF(),
|
||||
"flashMessage": ctx.FlashMessage(),
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/server/cookie"
|
||||
"github.com/miniflux/miniflux/server/core"
|
||||
"github.com/miniflux/miniflux/server/ui/form"
|
||||
|
||||
|
@ -23,7 +21,7 @@ func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, res
|
|||
}
|
||||
|
||||
response.HTML().Render("login", tplParams{
|
||||
"csrf": ctx.CsrfToken(),
|
||||
"csrf": ctx.CSRF(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -32,7 +30,7 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon
|
|||
authForm := form.NewAuthForm(request.Request())
|
||||
tplParams := tplParams{
|
||||
"errorMessage": "Invalid username or password.",
|
||||
"csrf": ctx.CsrfToken(),
|
||||
"csrf": ctx.CSRF(),
|
||||
}
|
||||
|
||||
if err := authForm.Validate(); err != nil {
|
||||
|
@ -60,15 +58,7 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon
|
|||
|
||||
logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username)
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "sessionID",
|
||||
Value: sessionToken,
|
||||
Path: "/",
|
||||
Secure: request.IsHTTPS(),
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
response.SetCookie(cookie)
|
||||
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, request.IsHTTPS()))
|
||||
response.Redirect(ctx.Route("unread"))
|
||||
}
|
||||
|
||||
|
@ -76,21 +66,10 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon
|
|||
func (c *Controller) Logout(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
|
||||
sessionCookie := request.Cookie("sessionID")
|
||||
if err := c.store.RemoveUserSessionByToken(user.ID, sessionCookie); err != nil {
|
||||
if err := c.store.RemoveUserSessionByToken(user.ID, ctx.UserSessionToken()); err != nil {
|
||||
logger.Error("[Controller:Logout] %v", err)
|
||||
}
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "sessionID",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Secure: request.IsHTTPS(),
|
||||
HttpOnly: true,
|
||||
MaxAge: -1,
|
||||
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
response.SetCookie(cookie)
|
||||
response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, request.IsHTTPS()))
|
||||
response.Redirect(ctx.Route("login"))
|
||||
}
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/miniflux/miniflux/config"
|
||||
"github.com/miniflux/miniflux/logger"
|
||||
"github.com/miniflux/miniflux/model"
|
||||
"github.com/miniflux/miniflux/server/cookie"
|
||||
"github.com/miniflux/miniflux/server/core"
|
||||
"github.com/miniflux/miniflux/server/oauth2"
|
||||
"github.com/tomasen/realip"
|
||||
|
@ -19,7 +18,7 @@ import (
|
|||
func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
provider := request.StringParam("provider", "")
|
||||
if provider == "" {
|
||||
logger.Error("[OAuth2] Invalid or missing provider")
|
||||
logger.Error("[OAuth2] Invalid or missing provider: %s", provider)
|
||||
response.Redirect(ctx.Route("login"))
|
||||
return
|
||||
}
|
||||
|
@ -31,7 +30,7 @@ func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, re
|
|||
return
|
||||
}
|
||||
|
||||
response.Redirect(authProvider.GetRedirectURL(ctx.CsrfToken()))
|
||||
response.Redirect(authProvider.GetRedirectURL(ctx.GenerateOAuth2State()))
|
||||
}
|
||||
|
||||
// OAuth2Callback receives the authorization code and create a new session.
|
||||
|
@ -51,8 +50,8 @@ func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, re
|
|||
}
|
||||
|
||||
state := request.QueryStringParam("state", "")
|
||||
if state != ctx.CsrfToken() {
|
||||
logger.Error("[OAuth2] Invalid state value")
|
||||
if state == "" || state != ctx.OAuth2State() {
|
||||
logger.Error(`[OAuth2] Invalid state value: got "%s" instead of "%s"`, state, ctx.OAuth2State())
|
||||
response.Redirect(ctx.Route("login"))
|
||||
return
|
||||
}
|
||||
|
@ -78,6 +77,7 @@ func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, re
|
|||
return
|
||||
}
|
||||
|
||||
ctx.SetFlashMessage(ctx.Translate("Your external account is now linked !"))
|
||||
response.Redirect(ctx.Route("settings"))
|
||||
return
|
||||
}
|
||||
|
@ -118,15 +118,7 @@ func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, re
|
|||
|
||||
logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username)
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "sessionID",
|
||||
Value: sessionToken,
|
||||
Path: "/",
|
||||
Secure: request.IsHTTPS(),
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
response.SetCookie(cookie)
|
||||
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, request.IsHTTPS()))
|
||||
response.Redirect(ctx.Route("unread"))
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/miniflux/miniflux/server/core"
|
||||
)
|
||||
|
||||
// ShowSessions shows the list of active sessions.
|
||||
// ShowSessions shows the list of active user sessions.
|
||||
func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
args, err := c.getCommonTemplateArgs(ctx)
|
||||
|
@ -24,15 +24,14 @@ func (c *Controller) ShowSessions(ctx *core.Context, request *core.Request, resp
|
|||
return
|
||||
}
|
||||
|
||||
sessionCookie := request.Cookie("sessionID")
|
||||
response.HTML().Render("sessions", args.Merge(tplParams{
|
||||
"sessions": sessions,
|
||||
"currentSessionToken": sessionCookie,
|
||||
"currentSessionToken": ctx.UserSessionToken(),
|
||||
"menu": "settings",
|
||||
}))
|
||||
}
|
||||
|
||||
// RemoveSession remove a session.
|
||||
// RemoveSession remove a user session.
|
||||
func (c *Controller) RemoveSession(ctx *core.Context, request *core.Request, response *core.Response) {
|
||||
user := ctx.LoggedUser()
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ func (c *Controller) UpdateSettings(ctx *core.Context, request *core.Request, re
|
|||
return
|
||||
}
|
||||
|
||||
ctx.SetFlashMessage(ctx.Translate("Preferences saved!"))
|
||||
response.Redirect(ctx.Route("settings"))
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,6 @@ func (c *Controller) ShowUnreadPage(ctx *core.Context, request *core.Request, re
|
|||
"entries": entries,
|
||||
"pagination": c.getPagination(ctx.Route("unread"), countUnread, offset),
|
||||
"menu": "unread",
|
||||
"csrf": ctx.CsrfToken(),
|
||||
"csrf": ctx.CSRF(),
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue