Implement basics of Gitea oAuth2 integration
Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
ab92d0566e
commit
0e46a3e12b
10 changed files with 226 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,3 +19,4 @@
|
|||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
run/*
|
||||
|
|
5
go.mod
5
go.mod
|
@ -17,6 +17,7 @@ require (
|
|||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
@ -27,5 +28,9 @@ require (
|
|||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
)
|
||||
|
|
21
go.sum
21
go.sum
|
@ -8,6 +8,11 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
|||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofiber/fiber/v2 v2.35.0 h1:ct+jKw8Qb24WEIZx3VV3zz9VXyBZL7mcEjNaqj3g0h0=
|
||||
github.com/gofiber/fiber/v2 v2.35.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
|
@ -52,9 +57,16 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
|
|||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -69,9 +81,18 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27sp
|
|||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/pkgerrors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func InitLogging() {
|
||||
|
@ -16,15 +17,17 @@ func InitLogging() {
|
|||
var Debug = struct {
|
||||
Enabled bool
|
||||
}{
|
||||
Enabled: asBool(get("debug.enable")),
|
||||
Enabled: asBool(get("debug.enabled")),
|
||||
}
|
||||
|
||||
var HTTP = struct {
|
||||
Host string
|
||||
Port int
|
||||
Host string
|
||||
Port int
|
||||
ExternalURL string
|
||||
}{
|
||||
Host: asString(withDefault("http.host", "0.0.0.0")),
|
||||
Port: asInt(withDefault("http.port", 8080)),
|
||||
Host: asString(withDefault("http.host", "0.0.0.0")),
|
||||
Port: asInt(withDefault("http.port", 8080)),
|
||||
ExternalURL: strings.TrimSuffix(asString(required("http.externalURL")), "/"),
|
||||
}
|
||||
|
||||
var Database = struct {
|
||||
|
@ -32,3 +35,13 @@ var Database = struct {
|
|||
}{
|
||||
Filename: asString(withDefault("db.filename", "database.db")),
|
||||
}
|
||||
|
||||
var Gitea = struct {
|
||||
BaseURL string
|
||||
OauthClientID string
|
||||
OauthClientSecret string
|
||||
}{
|
||||
BaseURL: strings.TrimSuffix(asString(required("gitea.baseURL")), "/"),
|
||||
OauthClientID: asString(required("gitea.oauth.clientID")),
|
||||
OauthClientSecret: asString(required("gitea.oauth.clientSecret")),
|
||||
}
|
||||
|
|
116
workboat/endpoints/auth.go
Normal file
116
workboat/endpoints/auth.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/codemicro/workboat/workboat/util"
|
||||
"github.com/codemicro/workboat/workboat/views"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type loginStateManager struct {
|
||||
states map[string]time.Time
|
||||
lock sync.Mutex
|
||||
stateTimeout time.Duration
|
||||
}
|
||||
|
||||
func newLoginStateManager() *loginStateManager {
|
||||
lsm := &loginStateManager{
|
||||
states: make(map[string]time.Time),
|
||||
stateTimeout: time.Minute * 5,
|
||||
}
|
||||
go lsm.Worker()
|
||||
return lsm
|
||||
}
|
||||
|
||||
func (lsm *loginStateManager) New() (string, error) {
|
||||
lsm.lock.Lock()
|
||||
defer lsm.lock.Unlock()
|
||||
|
||||
randData := make([]byte, 30)
|
||||
if _, err := rand.Read(randData); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
asBase64 := base64.URLEncoding.EncodeToString(randData)
|
||||
lsm.states[asBase64] = time.Now()
|
||||
return asBase64, nil
|
||||
}
|
||||
|
||||
func (lsm *loginStateManager) Use(state string) error {
|
||||
lsm.lock.Lock()
|
||||
defer lsm.lock.Unlock()
|
||||
|
||||
createdAt, found := lsm.states[state]
|
||||
if !found {
|
||||
return errors.New("unknown state")
|
||||
}
|
||||
|
||||
delete(lsm.states, state)
|
||||
|
||||
if time.Now().Sub(createdAt) > lsm.stateTimeout {
|
||||
return errors.New("expired state")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lsm *loginStateManager) Worker() {
|
||||
for {
|
||||
time.Sleep(lsm.stateTimeout)
|
||||
|
||||
lsm.lock.Lock()
|
||||
|
||||
var expired []string
|
||||
now := time.Now()
|
||||
for key, createdAt := range lsm.states {
|
||||
if now.Sub(createdAt) > lsm.stateTimeout {
|
||||
expired = append(expired, key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, expiredKey := range expired {
|
||||
delete(lsm.states, expiredKey)
|
||||
}
|
||||
|
||||
lsm.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoints) AuthLogin(ctx *fiber.Ctx) error {
|
||||
ctx.Type("html")
|
||||
return ctx.SendString(
|
||||
views.LoginPage(),
|
||||
)
|
||||
}
|
||||
|
||||
func (e *Endpoints) AuthOauthOutbound(ctx *fiber.Ctx) error {
|
||||
state, err := e.login.stateManager.New()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return ctx.Redirect(e.login.oauthConfig.AuthCodeURL(state))
|
||||
}
|
||||
|
||||
func (e *Endpoints) AuthOauthInbound(ctx *fiber.Ctx) error {
|
||||
stateFromRequest := ctx.Query("state")
|
||||
if err := e.login.stateManager.Use(stateFromRequest); err != nil {
|
||||
return util.NewRichError(fiber.StatusBadRequest, "invalid state", err.Error())
|
||||
}
|
||||
|
||||
exchangeCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
token, err := e.login.oauthConfig.Exchange(exchangeCtx, ctx.Query("code"))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// TODO: databaseify
|
||||
return ctx.SendString(fmt.Sprintf("%#v", token))
|
||||
}
|
|
@ -6,17 +6,34 @@ import (
|
|||
"github.com/codemicro/workboat/workboat/paths"
|
||||
"github.com/codemicro/workboat/workboat/util"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"golang.org/x/oauth2"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Endpoints struct {
|
||||
db *db.DB
|
||||
db *db.DB
|
||||
login struct {
|
||||
stateManager *loginStateManager
|
||||
oauthConfig *oauth2.Config
|
||||
}
|
||||
}
|
||||
|
||||
func New(dbi *db.DB) *Endpoints {
|
||||
return &Endpoints{
|
||||
db: dbi,
|
||||
e := new(Endpoints)
|
||||
|
||||
e.db = dbi
|
||||
e.login.stateManager = newLoginStateManager()
|
||||
e.login.oauthConfig = &oauth2.Config{
|
||||
ClientID: config.Gitea.OauthClientID,
|
||||
ClientSecret: config.Gitea.OauthClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: config.Gitea.BaseURL + "/login/oauth/authorize",
|
||||
TokenURL: config.Gitea.BaseURL + "/login/oauth/access_token",
|
||||
},
|
||||
RedirectURL: paths.Make(paths.AuthOauthInbound),
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Endpoints) SetupApp() *fiber.App {
|
||||
|
@ -29,6 +46,10 @@ func (e *Endpoints) SetupApp() *fiber.App {
|
|||
})
|
||||
|
||||
app.Get(paths.Index, e.Index)
|
||||
app.Get(paths.AuthLogin, e.AuthLogin)
|
||||
|
||||
app.Get(paths.AuthOauthOutbound, e.AuthOauthOutbound)
|
||||
app.Get(paths.AuthOauthInbound, e.AuthOauthInbound)
|
||||
|
||||
return app
|
||||
}
|
||||
|
|
|
@ -2,11 +2,19 @@ package paths
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/codemicro/workboat/workboat/config"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
Index = "/"
|
||||
|
||||
Auth = "/auth"
|
||||
AuthLogin = Auth + "/login"
|
||||
|
||||
AuthOauth = Auth + "/oauth"
|
||||
AuthOauthOutbound = AuthOauth + "/outbound"
|
||||
AuthOauthInbound = AuthOauth + "/inbound"
|
||||
)
|
||||
|
||||
func Make(path string, replacements ...any) string {
|
||||
|
@ -16,5 +24,11 @@ func Make(path string, replacements ...any) string {
|
|||
x[i] = "%s"
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(strings.Join(x, "/"), replacements...)
|
||||
|
||||
prepend := config.HTTP.ExternalURL
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
prepend += "/"
|
||||
}
|
||||
|
||||
return prepend + fmt.Sprintf(strings.Join(x, "/"), replacements...)
|
||||
}
|
||||
|
|
5
workboat/views/login.ntc
Normal file
5
workboat/views/login.ntc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{ import "github.com/codemicro/workboat/workboat/paths" }}
|
||||
|
||||
{{ func LoginPage() }}
|
||||
<a href="{[ paths.Make(paths.AuthOauthOutbound) ]}">Click here to login via Gitea</a>
|
||||
{{ endfunc }}
|
18
workboat/views/login.ntc.go
Normal file
18
workboat/views/login.ntc.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Code generated by Neon - DO NOT EDIT
|
||||
// https://github.com/codemicro/go-neon
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/codemicro/workboat/workboat/paths"
|
||||
ntcHCTQYeuVOf "html"
|
||||
ntcEAndIxAtps "strings"
|
||||
)
|
||||
|
||||
func LoginPage() string {
|
||||
ntcRijqFUuoGg := new(ntcEAndIxAtps.Builder)
|
||||
_, _ = ntcRijqFUuoGg.WriteString("\n <a href=\"")
|
||||
_, _ = ntcRijqFUuoGg.WriteString(ntcHCTQYeuVOf.EscapeString(paths.Make(paths.AuthOauthOutbound)))
|
||||
_, _ = ntcRijqFUuoGg.WriteString("\">Click here to login via Gitea</a>\n")
|
||||
return ntcRijqFUuoGg.String()
|
||||
}
|
3
workboat/views/views.go
Normal file
3
workboat/views/views.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package views
|
||||
|
||||
//go:generate neontc
|
Reference in a new issue