detached 187aa07

Add handlers for oauth, add session middleware, define routes for login/logout

commit
187aa07052802572ed780813931c87d5d393f454
author
bnema · · 2023-09-15 18:55 UTC
parent
e3bd247
signature
unsigned
5 files · +522 -32
M go.mod +8 -3
@@ -5,8 +5,11 @@ go 1.21.1
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
77 github.com/docker/docker v24.0.6+incompatible
8+ github.com/gorilla/sessions v1.2.1
9+ github.com/joho/godotenv v1.5.1
810 github.com/labstack/echo-contrib v0.15.0
911 github.com/labstack/echo/v4 v4.11.1
12+ github.com/markbates/goth v1.78.0
1013 github.com/mattn/go-sqlite3 v1.14.17
1114 gopkg.in/yaml.v3 v3.0.1
1215 )
@@ -20,11 +23,11 @@ require (
2023 github.com/docker/go-units v0.5.0 // indirect
2124 github.com/gogo/protobuf v1.3.2 // indirect
2225 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
26+ github.com/golang/protobuf v1.5.3 // indirect
2327 github.com/gorilla/context v1.1.1 // indirect
28+ github.com/gorilla/mux v1.6.2 // indirect
2429 github.com/gorilla/securecookie v1.1.1 // indirect
25- github.com/gorilla/sessions v1.2.1 // indirect
2630 github.com/klauspost/compress v1.16.7 // indirect
27- github.com/kr/text v0.1.0 // indirect
2831 github.com/labstack/gommon v0.4.0 // indirect
2932 github.com/mattn/go-colorable v0.1.13 // indirect
3033 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -43,10 +46,12 @@ require (
4346 golang.org/x/crypto v0.11.0 // indirect
4447 golang.org/x/mod v0.9.0 // indirect
4548 golang.org/x/net v0.12.0 // indirect
49+ golang.org/x/oauth2 v0.4.0 // indirect
4650 golang.org/x/sys v0.10.0 // indirect
4751 golang.org/x/text v0.11.0 // indirect
4852 golang.org/x/time v0.3.0 // indirect
4953 golang.org/x/tools v0.7.0 // indirect
50- gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
54+ google.golang.org/appengine v1.6.7 // indirect
55+ google.golang.org/protobuf v1.29.1 // indirect
5156 gotest.tools/v3 v3.5.0 // indirect
5257 )
@@ -5,8 +5,11 @@ go 1.21.1
5 require (5 require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7 github.com/docker/docker v24.0.6+incompatible7 github.com/docker/docker v24.0.6+incompatible
8+ github.com/gorilla/sessions v1.2.1
9+ github.com/joho/godotenv v1.5.1
8 github.com/labstack/echo-contrib v0.15.010 github.com/labstack/echo-contrib v0.15.0
9 github.com/labstack/echo/v4 v4.11.111 github.com/labstack/echo/v4 v4.11.1
12+ github.com/markbates/goth v1.78.0
10 github.com/mattn/go-sqlite3 v1.14.1713 github.com/mattn/go-sqlite3 v1.14.17
11 gopkg.in/yaml.v3 v3.0.114 gopkg.in/yaml.v3 v3.0.1
12 )15 )
@@ -20,11 +23,11 @@ require (
20 github.com/docker/go-units v0.5.0 // indirect23 github.com/docker/go-units v0.5.0 // indirect
21 github.com/gogo/protobuf v1.3.2 // indirect24 github.com/gogo/protobuf v1.3.2 // indirect
22 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect25 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
26+ github.com/golang/protobuf v1.5.3 // indirect
23 github.com/gorilla/context v1.1.1 // indirect27 github.com/gorilla/context v1.1.1 // indirect
28+ github.com/gorilla/mux v1.6.2 // indirect
24 github.com/gorilla/securecookie v1.1.1 // indirect29 github.com/gorilla/securecookie v1.1.1 // indirect
25- github.com/gorilla/sessions v1.2.1 // indirect
26 github.com/klauspost/compress v1.16.7 // indirect30 github.com/klauspost/compress v1.16.7 // indirect
27- github.com/kr/text v0.1.0 // indirect
28 github.com/labstack/gommon v0.4.0 // indirect31 github.com/labstack/gommon v0.4.0 // indirect
29 github.com/mattn/go-colorable v0.1.13 // indirect32 github.com/mattn/go-colorable v0.1.13 // indirect
30 github.com/mattn/go-isatty v0.0.19 // indirect33 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -43,10 +46,12 @@ require (
43 golang.org/x/crypto v0.11.0 // indirect46 golang.org/x/crypto v0.11.0 // indirect
44 golang.org/x/mod v0.9.0 // indirect47 golang.org/x/mod v0.9.0 // indirect
45 golang.org/x/net v0.12.0 // indirect48 golang.org/x/net v0.12.0 // indirect
49+ golang.org/x/oauth2 v0.4.0 // indirect
46 golang.org/x/sys v0.10.0 // indirect50 golang.org/x/sys v0.10.0 // indirect
47 golang.org/x/text v0.11.0 // indirect51 golang.org/x/text v0.11.0 // indirect
48 golang.org/x/time v0.3.0 // indirect52 golang.org/x/time v0.3.0 // indirect
49 golang.org/x/tools v0.7.0 // indirect53 golang.org/x/tools v0.7.0 // indirect
50- gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect54+ google.golang.org/appengine v1.6.7 // indirect
55+ google.golang.org/protobuf v1.29.1 // indirect
51 gotest.tools/v3 v3.5.0 // indirect56 gotest.tools/v3 v3.5.0 // indirect
52 )57 )
M go.sum +376 -0 generated file: collapsed
M internal/app/app.go +31 -28
@@ -18,41 +18,44 @@ const (
1818 AdminPath = "/admin"
1919 DockerSock = "/var/run/docker.sock"
2020 // If enabled replace all docker commands by podman
21- PodmanEnable = false
22- PodmanSock = "/run/user/1000/podman/podman.sock"
21+ PodmanEnable = false
22+ PodmanSock = "/run/user/1000/podman/podman.sock"
23+ OauthCallbackURL = "http://localhost:1323/oauth/callback"
2324 )
2425
2526 type App struct {
26- TemplateFS fs.FS
27- PublicFS fs.FS
28- BuildVersion string
29- BuildDir string
30- DBDir string
31- DBFilename string
32- DBPath string
33- InitialChecksum string
34- DB *sql.DB
35- TopDomain string
36- SubDomain string
37- AdminPath string
38- DockerSock string
39- PodmanEnable bool
40- PodmanSock string
41- HttpPort int16
27+ TemplateFS fs.FS
28+ PublicFS fs.FS
29+ BuildVersion string
30+ BuildDir string
31+ DBDir string
32+ DBFilename string
33+ DBPath string
34+ InitialChecksum string
35+ DB *sql.DB
36+ TopDomain string
37+ SubDomain string
38+ AdminPath string
39+ DockerSock string
40+ PodmanEnable bool
41+ PodmanSock string
42+ OauthCallbackURL string
43+ HttpPort int16
4244 }
4345
4446 func NewApp() *App {
4547 return &App{
46- TemplateFS: gotemplate.TemplateFS,
47- PublicFS: webui.PublicFS,
48- BuildVersion: BuildVersion,
49- BuildDir: BuildDir,
50- DBDir: DBDir,
51- DBFilename: DBFilename,
52- TopDomain: TopDomain,
53- SubDomain: SubDomain,
54- AdminPath: AdminPath,
55- HttpPort: HttpPort,
48+ TemplateFS: gotemplate.TemplateFS,
49+ PublicFS: webui.PublicFS,
50+ BuildVersion: BuildVersion,
51+ BuildDir: BuildDir,
52+ DBDir: DBDir,
53+ DBFilename: DBFilename,
54+ TopDomain: TopDomain,
55+ SubDomain: SubDomain,
56+ AdminPath: AdminPath,
57+ OauthCallbackURL: OauthCallbackURL,
58+ HttpPort: HttpPort,
5659 }
5760 }
5861
@@ -18,41 +18,44 @@ const (
18 AdminPath = "/admin"18 AdminPath = "/admin"
19 DockerSock = "/var/run/docker.sock"19 DockerSock = "/var/run/docker.sock"
20 // If enabled replace all docker commands by podman20 // If enabled replace all docker commands by podman
21- PodmanEnable = false21+ PodmanEnable = false
22- PodmanSock = "/run/user/1000/podman/podman.sock"22+ PodmanSock = "/run/user/1000/podman/podman.sock"
23+ OauthCallbackURL = "http://localhost:1323/oauth/callback"
23 )24 )
24 25
25 type App struct {26 type App struct {
26- TemplateFS fs.FS27+ TemplateFS fs.FS
27- PublicFS fs.FS28+ PublicFS fs.FS
28- BuildVersion string29+ BuildVersion string
29- BuildDir string30+ BuildDir string
30- DBDir string31+ DBDir string
31- DBFilename string32+ DBFilename string
32- DBPath string33+ DBPath string
33- InitialChecksum string34+ InitialChecksum string
34- DB *sql.DB35+ DB *sql.DB
35- TopDomain string36+ TopDomain string
36- SubDomain string37+ SubDomain string
37- AdminPath string38+ AdminPath string
38- DockerSock string39+ DockerSock string
39- PodmanEnable bool40+ PodmanEnable bool
40- PodmanSock string41+ PodmanSock string
41- HttpPort int1642+ OauthCallbackURL string
43+ HttpPort int16
42 }44 }
43 45
44 func NewApp() *App {46 func NewApp() *App {
45 return &App{47 return &App{
46- TemplateFS: gotemplate.TemplateFS,48+ TemplateFS: gotemplate.TemplateFS,
47- PublicFS: webui.PublicFS,49+ PublicFS: webui.PublicFS,
48- BuildVersion: BuildVersion,50+ BuildVersion: BuildVersion,
49- BuildDir: BuildDir,51+ BuildDir: BuildDir,
50- DBDir: DBDir,52+ DBDir: DBDir,
51- DBFilename: DBFilename,53+ DBFilename: DBFilename,
52- TopDomain: TopDomain,54+ TopDomain: TopDomain,
53- SubDomain: SubDomain,55+ SubDomain: SubDomain,
54- AdminPath: AdminPath,56+ AdminPath: AdminPath,
55- HttpPort: HttpPort,57+ OauthCallbackURL: OauthCallbackURL,
58+ HttpPort: HttpPort,
56 }59 }
57 }60 }
58 61
A internal/httpserve/handler/oauth.go +86 -0
@@ -0,0 +1,86 @@
1+package handler
2+
3+import (
4+ "github.com/bnema/gordon/internal/app"
5+ "github.com/bnema/gordon/internal/gotemplate/render"
6+ "github.com/bnema/gordon/internal/httpserve/middleware"
7+ "github.com/bnema/gordon/internal/webui"
8+ _ "github.com/joho/godotenv/autoload"
9+ "github.com/labstack/echo-contrib/session"
10+ "github.com/labstack/echo/v4"
11+ "github.com/markbates/goth"
12+ "github.com/markbates/goth/gothic"
13+ "github.com/markbates/goth/providers/github"
14+ "net/http"
15+)
16+
17+var providerNames []string
18+
19+func init() {
20+ goth.UseProviders(
21+ github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), app.OauthCallbackURL),
22+ )
23+ for _, provider := range goth.GetProviders() {
24+ // Append the provider's name to the slice
25+ providerNames = append(providerNames, provider.Name())
26+ }
27+}
28+
29+// RenderLoginPage renders the login.html template
30+func RenderLoginPage(c echo.Context, a *app.App) error {
31+ lang := c.Get(middleware.LangKey).(string)
32+ yamlData := webui.StringsYamlData{}
33+ err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
34+ if err != nil {
35+ return err
36+ }
37+
38+ // Navigate inside the fs.FS to get the template
39+ path := "html/login"
40+ rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a)
41+ if err != nil {
42+ return err
43+ }
44+
45+ renderedHTML, err := rendererData.Render(yamlData.CurrentLang, a)
46+ if err != nil {
47+ return err
48+ }
49+
50+ return c.HTML(200, renderedHTML)
51+}
52+
53+// StartOAuth initiates OAuth process
54+func StartOAuth(c echo.Context, a *app.App) error {
55+ provider := c.Param("provider")
56+ if provider == "" {
57+ return echo.NewHTTPError(http.StatusBadRequest, "Missing provider")
58+ }
59+
60+ // Get the current session from echo-contrib/session
61+ sess, _ := session.Get("session", c)
62+
63+ // Set the provider in the session
64+ sess.Values["provider"] = provider
65+ sess.Save(c.Request(), c.Response())
66+
67+ gothic.BeginAuthHandler(c.Response(), c.Request())
68+ return nil
69+}
70+
71+// OAuthCallback handles OAuth callback from provider
72+func OAuthCallback(c echo.Context, a *app.App) error {
73+ _, err := gothic.CompleteUserAuth(c.Response(), c.Request())
74+ if err != nil {
75+ return c.JSON(http.StatusUnauthorized, "Authentication failed")
76+ }
77+ // TODO: Add your logic here, like creating a session, etc.
78+ return c.Redirect(http.StatusMovedPermanently, "/success")
79+}
80+
81+// Logout handles user logout and clears session
82+func Logout(c echo.Context, a *app.App) error {
83+ gothic.Logout(c.Response(), c.Request())
84+ // TODO: Add your logout logic here, like clearing session, etc.
85+ return c.Redirect(http.StatusMovedPermanently, "/")
86+}
@@ -0,0 +1,86 @@
1+package handler
2+
3+import (
4+ "github.com/bnema/gordon/internal/app"
5+ "github.com/bnema/gordon/internal/gotemplate/render"
6+ "github.com/bnema/gordon/internal/httpserve/middleware"
7+ "github.com/bnema/gordon/internal/webui"
8+ _ "github.com/joho/godotenv/autoload"
9+ "github.com/labstack/echo-contrib/session"
10+ "github.com/labstack/echo/v4"
11+ "github.com/markbates/goth"
12+ "github.com/markbates/goth/gothic"
13+ "github.com/markbates/goth/providers/github"
14+ "net/http"
15+)
16+
17+var providerNames []string
18+
19+func init() {
20+ goth.UseProviders(
21+ github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), app.OauthCallbackURL),
22+ )
23+ for _, provider := range goth.GetProviders() {
24+ // Append the provider's name to the slice
25+ providerNames = append(providerNames, provider.Name())
26+ }
27+}
28+
29+// RenderLoginPage renders the login.html template
30+func RenderLoginPage(c echo.Context, a *app.App) error {
31+ lang := c.Get(middleware.LangKey).(string)
32+ yamlData := webui.StringsYamlData{}
33+ err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
34+ if err != nil {
35+ return err
36+ }
37+
38+ // Navigate inside the fs.FS to get the template
39+ path := "html/login"
40+ rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a)
41+ if err != nil {
42+ return err
43+ }
44+
45+ renderedHTML, err := rendererData.Render(yamlData.CurrentLang, a)
46+ if err != nil {
47+ return err
48+ }
49+
50+ return c.HTML(200, renderedHTML)
51+}
52+
53+// StartOAuth initiates OAuth process
54+func StartOAuth(c echo.Context, a *app.App) error {
55+ provider := c.Param("provider")
56+ if provider == "" {
57+ return echo.NewHTTPError(http.StatusBadRequest, "Missing provider")
58+ }
59+
60+ // Get the current session from echo-contrib/session
61+ sess, _ := session.Get("session", c)
62+
63+ // Set the provider in the session
64+ sess.Values["provider"] = provider
65+ sess.Save(c.Request(), c.Response())
66+
67+ gothic.BeginAuthHandler(c.Response(), c.Request())
68+ return nil
69+}
70+
71+// OAuthCallback handles OAuth callback from provider
72+func OAuthCallback(c echo.Context, a *app.App) error {
73+ _, err := gothic.CompleteUserAuth(c.Response(), c.Request())
74+ if err != nil {
75+ return c.JSON(http.StatusUnauthorized, "Authentication failed")
76+ }
77+ // TODO: Add your logic here, like creating a session, etc.
78+ return c.Redirect(http.StatusMovedPermanently, "/success")
79+}
80+
81+// Logout handles user logout and clears session
82+func Logout(c echo.Context, a *app.App) error {
83+ gothic.Logout(c.Response(), c.Request())
84+ // TODO: Add your logout logic here, like clearing session, etc.
85+ return c.Redirect(http.StatusMovedPermanently, "/")
86+}
M internal/httpserve/router.go +21 -1
@@ -1,9 +1,14 @@
11 package httpserve
22
33 import (
4+ "os"
5+
46 "github.com/bnema/gordon/internal/app"
57 "github.com/bnema/gordon/internal/httpserve/handler"
68 "github.com/bnema/gordon/internal/httpserve/middleware"
9+ "github.com/gorilla/sessions"
10+ _ "github.com/joho/godotenv/autoload"
11+ "github.com/labstack/echo-contrib/session"
712 "github.com/labstack/echo/v4"
813 )
914
@@ -11,7 +16,9 @@ import (
1116 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
1217 AdminPath := a.AdminPath
1318 e.Use(middleware.ErrorHandler)
14- // Use middlewares
19+ // Use session middleware
20+ // Add session middleware
21+ e.Use(session.Middleware(sessions.NewCookieStore([]byte(os.Getenv("SECRET")))))
1522 // e.Use(middleware.SecureRoutes())
1623 e.Use(middleware.ColorSchemeDetection)
1724 e.Use(middleware.LanguageDetection)
@@ -27,6 +34,19 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
2734 return c.String(403, "403 Forbidden")
2835 })
2936
37+ e.GET(a.AdminPath+"/login", func(c echo.Context) error {
38+ return handler.RenderLoginPage(c, a)
39+ })
40+ e.GET(a.AdminPath+"/login/:provider", func(c echo.Context) error {
41+ return handler.StartOAuth(c, a)
42+ })
43+ e.GET(a.OauthCallbackURL, func(c echo.Context) error {
44+ return handler.OAuthCallback(c, a)
45+ })
46+ e.GET(a.AdminPath+"/logout", func(c echo.Context) error {
47+ return handler.Logout(c, a)
48+ })
49+
3050 return e
3151 }
3252
@@ -1,9 +1,14 @@
1 package httpserve1 package httpserve
2 2
3 import (3 import (
4+ "os"
5+
4 "github.com/bnema/gordon/internal/app"6 "github.com/bnema/gordon/internal/app"
5 "github.com/bnema/gordon/internal/httpserve/handler"7 "github.com/bnema/gordon/internal/httpserve/handler"
6 "github.com/bnema/gordon/internal/httpserve/middleware"8 "github.com/bnema/gordon/internal/httpserve/middleware"
9+ "github.com/gorilla/sessions"
10+ _ "github.com/joho/godotenv/autoload"
11+ "github.com/labstack/echo-contrib/session"
7 "github.com/labstack/echo/v4"12 "github.com/labstack/echo/v4"
8 )13 )
9 14
@@ -11,7 +16,9 @@ import (
11 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {16 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
12 AdminPath := a.AdminPath17 AdminPath := a.AdminPath
13 e.Use(middleware.ErrorHandler)18 e.Use(middleware.ErrorHandler)
14- // Use middlewares19+ // Use session middleware
20+ // Add session middleware
21+ e.Use(session.Middleware(sessions.NewCookieStore([]byte(os.Getenv("SECRET")))))
15 // e.Use(middleware.SecureRoutes())22 // e.Use(middleware.SecureRoutes())
16 e.Use(middleware.ColorSchemeDetection)23 e.Use(middleware.ColorSchemeDetection)
17 e.Use(middleware.LanguageDetection)24 e.Use(middleware.LanguageDetection)
@@ -27,6 +34,19 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
27 return c.String(403, "403 Forbidden")34 return c.String(403, "403 Forbidden")
28 })35 })
29 36
37+ e.GET(a.AdminPath+"/login", func(c echo.Context) error {
38+ return handler.RenderLoginPage(c, a)
39+ })
40+ e.GET(a.AdminPath+"/login/:provider", func(c echo.Context) error {
41+ return handler.StartOAuth(c, a)
42+ })
43+ e.GET(a.OauthCallbackURL, func(c echo.Context) error {
44+ return handler.OAuthCallback(c, a)
45+ })
46+ e.GET(a.AdminPath+"/logout", func(c echo.Context) error {
47+ return handler.Logout(c, a)
48+ })
49+
30 return e50 return e
31 }51 }
32 52

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close