diff options
author | 2017-11-22 22:22:33 -0800 | |
---|---|---|
committer | 2017-11-22 22:22:33 -0800 | |
commit | cc6d272eb7719bcca02c7f36a1badbeecb153759 (patch) | |
tree | 2fd6e92dcaa19faccfc25ff67499abcc6dabbaaf /server/ui | |
parent | 9877051f12621aa71daad520caa2847c47c746f8 (diff) |
Add OAuth2 authentication
Diffstat (limited to 'server/ui')
-rw-r--r-- | server/ui/controller/controller.go | 5 | ||||
-rw-r--r-- | server/ui/controller/login.go | 9 | ||||
-rw-r--r-- | server/ui/controller/oauth2.go | 123 |
3 files changed, 134 insertions, 3 deletions
diff --git a/server/ui/controller/controller.go b/server/ui/controller/controller.go index 4aa6085..a8f5218 100644 --- a/server/ui/controller/controller.go +++ b/server/ui/controller/controller.go @@ -5,6 +5,7 @@ package controller import ( + "github.com/miniflux/miniflux2/config" "github.com/miniflux/miniflux2/model" "github.com/miniflux/miniflux2/reader/feed" "github.com/miniflux/miniflux2/reader/opml" @@ -25,6 +26,7 @@ func (t tplParams) Merge(d tplParams) tplParams { // Controller contains all HTTP handlers for the user interface. type Controller struct { + cfg *config.Config store *storage.Storage pool *scheduler.WorkerPool feedHandler *feed.Handler @@ -51,8 +53,9 @@ func (c *Controller) getCommonTemplateArgs(ctx *core.Context) (tplParams, error) } // NewController returns a new Controller. -func NewController(store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, opmlHandler *opml.Handler) *Controller { +func NewController(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, opmlHandler *opml.Handler) *Controller { return &Controller{ + cfg: cfg, store: store, pool: pool, feedHandler: feedHandler, diff --git a/server/ui/controller/login.go b/server/ui/controller/login.go index aaaf0b6..2571f6e 100644 --- a/server/ui/controller/login.go +++ b/server/ui/controller/login.go @@ -5,15 +5,17 @@ package controller import ( - "github.com/miniflux/miniflux2/server/core" - "github.com/miniflux/miniflux2/server/ui/form" "log" "net/http" "time" + "github.com/miniflux/miniflux2/server/core" + "github.com/miniflux/miniflux2/server/ui/form" + "github.com/tomasen/realip" ) +// ShowLoginPage shows the login form. func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, response *core.Response) { if ctx.IsAuthenticated() { response.Redirect(ctx.Route("unread")) @@ -25,6 +27,7 @@ func (c *Controller) ShowLoginPage(ctx *core.Context, request *core.Request, res }) } +// CheckLogin validates the username/password and redirects the user to the unread page. func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, response *core.Response) { authForm := form.NewAuthForm(request.Request()) tplParams := tplParams{ @@ -49,6 +52,7 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon request.Request().UserAgent(), realip.RealIP(request.Request()), ) + if err != nil { response.HTML().ServerError(err) return @@ -68,6 +72,7 @@ func (c *Controller) CheckLogin(ctx *core.Context, request *core.Request, respon response.Redirect(ctx.Route("unread")) } +// Logout destroy the session and redirects the user to the login page. func (c *Controller) Logout(ctx *core.Context, request *core.Request, response *core.Response) { user := ctx.LoggedUser() diff --git a/server/ui/controller/oauth2.go b/server/ui/controller/oauth2.go new file mode 100644 index 0000000..c43d707 --- /dev/null +++ b/server/ui/controller/oauth2.go @@ -0,0 +1,123 @@ +// 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 controller + +import ( + "log" + "net/http" + + "github.com/miniflux/miniflux2/config" + "github.com/miniflux/miniflux2/model" + "github.com/miniflux/miniflux2/server/core" + "github.com/miniflux/miniflux2/server/oauth2" + "github.com/tomasen/realip" +) + +// OAuth2Redirect redirects the user to the consent page to ask for permission. +func (c *Controller) OAuth2Redirect(ctx *core.Context, request *core.Request, response *core.Response) { + provider := request.StringParam("provider", "") + if provider == "" { + log.Println("[OAuth2] Invalid or missing provider") + response.Redirect(ctx.Route("login")) + return + } + + authProvider, err := getOAuth2Manager(c.cfg).Provider(provider) + if err != nil { + log.Println("[OAuth2]", err) + response.Redirect(ctx.Route("login")) + return + } + + response.Redirect(authProvider.GetRedirectURL(ctx.CsrfToken())) +} + +// OAuth2Callback receives the authorization code and create a new session. +func (c *Controller) OAuth2Callback(ctx *core.Context, request *core.Request, response *core.Response) { + provider := request.StringParam("provider", "") + if provider == "" { + log.Println("[OAuth2] Invalid or missing provider") + response.Redirect(ctx.Route("login")) + return + } + + code := request.QueryStringParam("code", "") + if code == "" { + log.Println("[OAuth2] No code received on callback") + response.Redirect(ctx.Route("login")) + return + } + + state := request.QueryStringParam("state", "") + if state != ctx.CsrfToken() { + log.Println("[OAuth2] Invalid state value") + response.Redirect(ctx.Route("login")) + return + } + + authProvider, err := getOAuth2Manager(c.cfg).Provider(provider) + if err != nil { + log.Println("[OAuth2]", err) + response.Redirect(ctx.Route("login")) + return + } + + profile, err := authProvider.GetProfile(code) + if err != nil { + log.Println("[OAuth2]", err) + response.Redirect(ctx.Route("login")) + return + } + + user, err := c.store.GetUserByExtraField(profile.Key, profile.ID) + if err != nil { + response.HTML().ServerError(err) + return + } + + if user == nil { + user = model.NewUser() + user.Username = profile.Username + user.IsAdmin = false + user.Extra[profile.Key] = profile.ID + + if err := c.store.CreateUser(user); err != nil { + response.HTML().ServerError(err) + return + } + } + + sessionToken, err := c.store.CreateSession( + user.Username, + request.Request().UserAgent(), + realip.RealIP(request.Request()), + ) + + if err != nil { + response.HTML().ServerError(err) + return + } + + log.Printf("[UI:OAuth2Callback] username=%s just logged in\n", user.Username) + + cookie := &http.Cookie{ + Name: "sessionID", + Value: sessionToken, + Path: "/", + Secure: request.IsHTTPS(), + HttpOnly: true, + } + + response.SetCookie(cookie) + response.Redirect(ctx.Route("unread")) +} + +func getOAuth2Manager(cfg *config.Config) *oauth2.Manager { + return oauth2.NewManager( + cfg.Get("OAUTH2_CLIENT_ID", ""), + cfg.Get("OAUTH2_CLIENT_SECRET", ""), + cfg.Get("OAUTH2_REDIRECT_URL", ""), + ) +} |