From 6f379cc12c0602b3fef2df28c25ce108952ae064 Mon Sep 17 00:00:00 2001 From: Meutel Date: Sun, 8 Sep 2019 10:41:10 +0200 Subject: [PATCH] Gitea oauth2 provider --- assets/css/bouquins.css | 4 + assets/css/gitea.svg | 160 ++++++++++++++++++++++++++++++++++++++++ bouquins/auth.go | 14 +++- bouquins/bouquins.go | 3 + bouquins/gitea.go | 94 +++++++++++++++++++++++ bouquins/github.go | 11 +-- bouquins/google.go | 2 +- 7 files changed, 275 insertions(+), 13 deletions(-) create mode 100644 assets/css/gitea.svg create mode 100644 bouquins/gitea.go diff --git a/assets/css/bouquins.css b/assets/css/bouquins.css index 1ac8b26..ea75d89 100644 --- a/assets/css/bouquins.css +++ b/assets/css/bouquins.css @@ -12,3 +12,7 @@ span.providericon { .googleicon { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB1FBMVEUAAAD/AADsQzXrQzbqQzXpRDTqQzXqQjXqQzXqRDToRjbqQzXqQzXqQzXqRDXrRTHxRznqQzXpQzXqQzXpQzTjOTnrQTTqQzXqQzToRDPpQjfqQzXqQzXpQzbsRDjqQzXoRDfqQzXqQzXrQzXqRDXqQjXqQzbqQzXqQzXqQzTsQjn/rxDqRDTqQzXqRDXoRjr8vAX5sQrsTDHrQjT//wD7vAT7ugbvZif6vgX1jRjqQzX7vAT5sAo/jss9kb77uwRAieH6vQX6vQX6vAX6vQVBiOnkuA4/i9r6vAWstCQ1qVI3pFI9k7E+j8n4vAZvrT00qFM0p1M/jso+lLn8vAXkug1CqU00qFQ8lK45l6ffvxg3qFI0qFM9lqpBieU/jc00qFQzqFM1p09An2A0plk/jdA5lqwtpVo0qFM0qFM1qVQ1qFM1p1I0p1IzqVI1qFM1qFM+j8gzplM0qFNAi91Cl6o1qVM0qFM/jNQ7m602qFE0qFM1qFMxpVI0qVM0qFM0qFM0qFQktkk4p1A0qFM0qVM0qVMzo1IA/wA2p1M0qFIzqFIzp1QzqFI0p1PqQzX7vAVChfRChu9BhvA0qFNChfJBhfM1p1o9krs5mpQ3oHf////8WgVEAAAAj3RSTlMAATVylKafh24xIZXl5o8aEpD5940JJ9nWLUby8Tkp8Dje+rt8YF/7/pwbIOX2VhaV58xZAeP7xTfK63Kv47x8+6VgpGH+sfU1y+wcfd/7xv6d36SU6NJYYjEg5fdL/OePnx0IcPxQEdz7vX5gXXOl7tko8PgbRPH6OCbYzB+O+PaJByCT4+YZATRwpIZtMQ4TRwgAAAABYktHRJvv2FeEAAAAB3RJTUUH4QgKAjghFnOx6QAAAWBJREFUOMtjYCABMDIxs7CysXNwMmKV5uLm6YcCXj5+DGkBQaF+JCAsIooqLybejwYkJJHlpaTR5ftlZJHk5eQx5RWQ7VeEiiopq6iqqSiro+lnEIRIa2hqQf3DiqKfQVsHLK+rhxDSR/GBgaERSL8xrvAzMZ1gZq7Rr4kzgC0mAIGllRZOBdYgBRNsYFxbZGAHdgJYgT1MwURk4AAScQQrcMKqYBJIxBmswAWrgsmErHBFONINqwJ3kIgHSN7TyxvVbz5gBb7QgPLzD5gSiKogCKwgGMwOCQ2bMmVKQDiyfMRUkPy0SDAnKnoKCMQgqYiNAxsQD+UmgBVMCUhMgvCTU1LB8lPToArSMyAqpmRmZefk5uUXTJk+A6SgEG5iUfEUdDBz8sSSUoSdZeUYKmZVVCK7uqoaXUFNLaq/0+vqkaUbGpsw0kVzSytMui2hHWvS6ejsaulO7Ont6yAlywMAh+DsfszQdOIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDgtMTBUMDI6NTY6MzMrMDA6MDAy1cN5AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA4LTEwVDAyOjU2OjMzKzAwOjAwQ4h7xQAAAABJRU5ErkJggg==); } + +.giteaicon { + background-image: url(./gitea.svg); +} diff --git a/assets/css/gitea.svg b/assets/css/gitea.svg new file mode 100644 index 0000000..ac1594a --- /dev/null +++ b/assets/css/gitea.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/bouquins/auth.go b/bouquins/auth.go index 59af111..5f39204 100644 --- a/bouquins/auth.go +++ b/bouquins/auth.go @@ -33,18 +33,28 @@ type LoginModel struct { // NewLoginModel constructor for LoginModel func (app *Bouquins) NewLoginModel(req *http.Request) *LoginModel { + // TODO filter configured providers return &LoginModel{*app.NewModel("Authentification", "provider", req), Providers} } // OAuth2Provider allows to get a user from an OAuth2 token type OAuth2Provider interface { - GetUser(token *oauth2.Token) (string, error) + GetUser(app *Bouquins, token *oauth2.Token) (string, error) Config(conf *Conf) *oauth2.Config Name() string Label() string Icon() string } +func findProvider(name string) OAuth2Provider { + for _, p := range Providers { + if p.Name() == name { + return p + } + } + return nil +} + // generates a 16 characters long random string func securedRandString() string { b := make([]byte, 16) @@ -123,7 +133,7 @@ func (app *Bouquins) CallbackPage(res http.ResponseWriter, req *http.Request) er if err != nil { return fmt.Errorf("Code exchange failed with '%s'", err) } - userEmail, err := provider.GetUser(token) + userEmail, err := provider.GetUser(app, token) if err != nil { return err } diff --git a/bouquins/bouquins.go b/bouquins/bouquins.go index 7f3374d..79faefa 100644 --- a/bouquins/bouquins.go +++ b/bouquins/bouquins.go @@ -85,6 +85,9 @@ type ProviderConf struct { Name string `json:"name"` ClientID string `json:"client-id"` ClientSecret string `json:"client-secret"` + AuthURL string `json:"auth-url"` + TokenURL string `json:"token-url"` + ProfileURL string `json:"profile-url"` } // Bouquins contains application common resources: templates, database diff --git a/bouquins/gitea.go b/bouquins/gitea.go new file mode 100644 index 0000000..98dea22 --- /dev/null +++ b/bouquins/gitea.go @@ -0,0 +1,94 @@ +package bouquins + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "golang.org/x/oauth2" +) + +// GithubProvider implements OAuth2 client with github.com +type GiteaProvider string + +type giteaProfile struct { + AvatarURL string `json:"avatar_url"` + Created string `json:"created"` + Email string `json:"email"` + FullName string `json:"full_name"` + ID int64 `json:"id"` + IsAdmin bool `json:"is_admin"` + Language string `json:"language"` + LastLogin string `json:"last_login"` + Login string `json:"login"` +} + +func init() { + Providers = append(Providers, GiteaProvider("gitea")) +} + +// Name returns name of provider +func (p GiteaProvider) Name() string { + return string(p) +} + +// Label returns label of provider +func (p GiteaProvider) Label() string { + return "Gitea" +} + +// ProfileURL returns redirect URL for oauth2 auth +func (p GiteaProvider) ProfileURL(conf *Conf) string { + for _, c := range conf.ProvidersConf { + if c.Name == p.Name() { + return c.ProfileURL + } + } + return "" +} + +// Icon returns icon CSS class for provider +func (p GiteaProvider) Icon() string { + return "giteaicon" +} + +// Config returns OAuth configuration for this provider +func (p GiteaProvider) Config(conf *Conf) *oauth2.Config { + for _, c := range conf.ProvidersConf { + if c.Name == p.Name() { + return &oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + RedirectURL: conf.ExternalURL + "/callback", + Endpoint: oauth2.Endpoint{c.AuthURL, c.TokenURL, oauth2.AuthStyleAutoDetect}, + } + } + } + return nil +} + +// GetUser returns github primary email +func (p GiteaProvider) GetUser(app *Bouquins, token *oauth2.Token) (string, error) { + apiReq, err := http.NewRequest("GET", p.ProfileURL(app.Conf), nil) + apiReq.Header.Add("Accept", "application/json") + apiReq.Header.Add("Authorization", "token "+token.AccessToken) + client := &http.Client{} + response, err := client.Do(apiReq) + defer response.Body.Close() + if err != nil { + log.Println("Auth error", err) + return "", fmt.Errorf("Authentification error") + } + + dec := json.NewDecoder(response.Body) + var profile giteaProfile + err = dec.Decode(&profile) + if err != nil { + log.Printf("Error reading %s API response %v", p.Name(), err) + return "", fmt.Errorf("Error reading %s API response", p.Name()) + } + userEmail := profile.Email + log.Println("User email:", userEmail) + return userEmail, nil +} diff --git a/bouquins/github.go b/bouquins/github.go index 9355881..0a6d6ed 100644 --- a/bouquins/github.go +++ b/bouquins/github.go @@ -55,7 +55,7 @@ func (p GithubProvider) Config(conf *Conf) *oauth2.Config { } // GetUser returns github primary email -func (p GithubProvider) GetUser(token *oauth2.Token) (string, error) { +func (p GithubProvider) GetUser(app *Bouquins, token *oauth2.Token) (string, error) { apiReq, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil) apiReq.Header.Add("Accept", "application/vnd.github.v3+json") apiReq.Header.Add("Authorization", "token "+token.AccessToken) @@ -83,12 +83,3 @@ func (p GithubProvider) GetUser(token *oauth2.Token) (string, error) { log.Println("User email:", userEmail) return userEmail, nil } - -func findProvider(name string) OAuth2Provider { - for _, p := range Providers { - if p.Name() == name { - return p - } - } - return nil -} diff --git a/bouquins/google.go b/bouquins/google.go index fb11cd2..cebd669 100644 --- a/bouquins/google.go +++ b/bouquins/google.go @@ -60,7 +60,7 @@ func (p GoogleProvider) Config(conf *Conf) *oauth2.Config { } // GetUser returns github primary email -func (p GoogleProvider) GetUser(token *oauth2.Token) (string, error) { +func (p GoogleProvider) GetUser(app *Bouquins, token *oauth2.Token) (string, error) { apiRes, err := http.Post("https://www.googleapis.com/oauth2/v2/tokeninfo?access_token="+token.AccessToken, "application/json", nil) defer apiRes.Body.Close() if err != nil {