From 1dcb900cf31a2ae10edb6d47b3c4a478b0a083a8 Mon Sep 17 00:00:00 2001 From: Gustavo Maronato Date: Fri, 15 Sep 2023 18:56:14 -0300 Subject: [PATCH 1/4] implement OIDC auth --- README.md | 25 ++++++++++++++++++++---- config.yml | 8 +++++++- internal/config/config.go | 8 ++++++++ internal/models/user.go | 8 ++++++++ internal/web/auth.go | 32 ++++++++++++++++++++++++++++++- internal/web/run.go | 1 + templates/pages/admin_config.html | 3 +++ templates/pages/auth_form.html | 7 ++++++- templates/pages/settings.html | 16 ++++++++++++++-- 9 files changed, 99 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 30253ac..c392224 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic * Search for snippets ; browse users snippets, likes and forks * Editor with indentation mode & size ; drag and drop files * Download raw files or as a ZIP archive -* OAuth2 login with GitHub and Gitea +* OAuth2 login with GitHub, Gitea, and OpenID Connect * Avatars via Gravatar or OAuth2 providers * Light/Dark mode * Responsive UI @@ -113,8 +113,8 @@ You would only need to specify the configuration options you want to change —
Configuration option list -| YAML Config Key | Environment Variable | Default value | Description | -|-----------------------|--------------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| YAML Config Key | Environment Variable | Default value | Description | +| --------------------- | ------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. | | external-url | OG_EXTERNAL_URL | none | Public URL for the Git HTTP/SSH connection. If not set, uses the URL from the request. | | opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. | @@ -136,6 +136,9 @@ You would only need to specify the configuration options you want to change — | gitea.client-key | OG_GITEA_CLIENT_KEY | none | The client key for the Gitea OAuth application. | | gitea.secret | OG_GITEA_SECRET | none | The secret for the Gitea OAuth application. | | gitea.url | OG_GITEA_URL | `https://gitea.com/` | The URL of the Gitea instance. | +| oidc.client-key | OG_OIDC_CLIENT_KEY | none | The client key for the OpenID application. | +| oidc.secret | OG_OIDC_SECRET | none | The secret for the OpenID application. | +| oidc.discovery-url | OG_OIDC_DISCOVERY_URL | none | Discovery endpoint of the OpenID provider. |
@@ -224,7 +227,7 @@ service fail2ban restart ## Configure OAuth -Opengist can be configured to use OAuth to authenticate users, with GitHub or Gitea. +Opengist can be configured to use OAuth to authenticate users, with GitHub, Gitea, or OpenID Connect.
Integrate Github @@ -252,6 +255,20 @@ Opengist can be configured to use OAuth to authenticate users, with GitHub or Gi ```
+
+Integrate OpenID + +* Add a new OAuth app in Application settings of your OIDC provider +* Set 'Redirect URI' to `http://opengist.domain/oauth/openid-connect/callback` +* Copy the 'Client ID', 'Client Secret', and the discovery endpoint, and add them to the configuration : + ```yaml + oidc.client-key: + oidc.secret: + # Discovery endpoint of the OpenID provider + oidc.url: http://auth.example.com/.well-known/openid-configuration + ``` +
+ ## License Opengist is licensed under the [AGPL-3.0 license](LICENSE). diff --git a/config.yml b/config.yml index 04b9889..1cc77b4 100644 --- a/config.yml +++ b/config.yml @@ -60,7 +60,7 @@ ssh.keygen-executable: ssh-keygen # OAuth2 configuration -# The callback/redirect URL must be http://opengist.domain/oauth//callback +# The callback/redirect URL must be http://opengist.domain/oauth//callback # To create a new OAuth2 application using GitHub : https://github.com/settings/applications/new github.client-key: @@ -71,3 +71,9 @@ gitea.client-key: gitea.secret: # URL of the Gitea instance. Default: https://gitea.com/ gitea.url: https://gitea.com/ + +# To create a new OAuth2 application using OpenID Connect: +oidc.client-key: +oidc.secret: +# Discovery endpoint of the OpenID provider +oidc.discovery-url: diff --git a/internal/config/config.go b/internal/config/config.go index c1c3745..492a40e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -48,6 +48,10 @@ type config struct { GiteaClientKey string `yaml:"gitea.client-key" env:"OG_GITEA_CLIENT_KEY"` GiteaSecret string `yaml:"gitea.secret" env:"OG_GITEA_SECRET"` GiteaUrl string `yaml:"gitea.url" env:"OG_GITEA_URL"` + + OIDCClientKey string `yaml:"oidc.client-key" env:"OG_OIDC_CLIENT_KEY"` + OIDCSecret string `yaml:"oidc.secret" env:"OG_OIDC_SECRET"` + OIDCDiscoveryUrl string `yaml:"oidc.discovery-url" env:"OG_OIDC_DISCOVERY_URL"` } func configWithDefaults() (*config, error) { @@ -237,5 +241,9 @@ func checks(c *config) error { return err } + if _, err := url.Parse(c.OIDCDiscoveryUrl); err != nil { + return err + } + return nil } diff --git a/internal/models/user.go b/internal/models/user.go index 9208d0d..b9be02f 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -15,6 +15,7 @@ type User struct { AvatarURL string GithubID string GiteaID string + OIDCID string `gorm:"column:oidc_id"` Gists []Gist `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:UserID"` SSHKeys []SSHKey `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:UserID"` @@ -124,6 +125,8 @@ func GetUserByProvider(id string, provider string) (*User, error) { err = db.Where("github_id = ?", id).First(&user).Error case "gitea": err = db.Where("gitea_id = ?", id).First(&user).Error + case "openid-connect": + err = db.Where("oidc_id = ?", id).First(&user).Error } return user, err @@ -169,6 +172,11 @@ func (user *User) DeleteProviderID(provider string) error { Update("gitea_id", nil). Update("avatar_url", nil). Error + case "openid-connect": + return db.Model(&user). + Update("oidc_id", nil). + Update("avatar_url", nil). + Error } return nil diff --git a/internal/web/auth.go b/internal/web/auth.go index a5a4e9d..2ef4096 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -16,6 +16,7 @@ import ( "github.com/markbates/goth/gothic" "github.com/markbates/goth/providers/gitea" "github.com/markbates/goth/providers/github" + "github.com/markbates/goth/providers/openidConnect" "github.com/rs/zerolog/log" "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/models" @@ -150,6 +151,9 @@ func oauthCallback(ctx echo.Context) error { case "gitea": currUser.GiteaID = user.UserID currUser.AvatarURL = getAvatarUrlFromProvider("gitea", user.NickName) + case "openid-connect": + currUser.OIDCID = user.UserID + currUser.AvatarURL = user.AvatarURL } if err = currUser.Update(); err != nil { @@ -185,6 +189,9 @@ func oauthCallback(ctx echo.Context) error { case "gitea": userDB.GiteaID = user.UserID userDB.AvatarURL = getAvatarUrlFromProvider("gitea", user.NickName) + case "openid-connect": + userDB.OIDCID = user.UserID + userDB.AvatarURL = user.AvatarURL } if err = userDB.Create(); err != nil { @@ -208,6 +215,8 @@ func oauthCallback(ctx echo.Context) error { resp, err = http.Get("https://github.com/" + user.NickName + ".keys") case "gitea": resp, err = http.Get(urlJoin(config.C.GiteaUrl, user.NickName+".keys")) + case "openid-connect": + err = errors.New("Cannot get keys from OIDC provider") } if err == nil { @@ -282,6 +291,22 @@ func oauth(ctx echo.Context) error { urlJoin(config.C.GiteaUrl, "/api/v1/user"), ), ) + case "openid-connect": + oidcProvider, err := openidConnect.New( + config.C.OIDCClientKey, + config.C.OIDCSecret, + urlJoin(opengistUrl, "/oauth/openid-connect/callback"), + config.C.OIDCDiscoveryUrl, + "openid", + "email", + "profile", + ) + + if err != nil { + return errorRes(500, "Cannot create OIDC provider", err) + } + + goth.UseProviders(oidcProvider) } currUser := getUserLogged(ctx) @@ -299,6 +324,11 @@ func oauth(ctx echo.Context) error { isDelete = true err = currUser.DeleteProviderID(provider) } + case "openid-connect": + if currUser.OIDCID != "" { + isDelete = true + err = currUser.DeleteProviderID(provider) + } } if err != nil { @@ -313,7 +343,7 @@ func oauth(ctx echo.Context) error { ctxValue := context.WithValue(ctx.Request().Context(), gothic.ProviderParamKey, provider) ctx.SetRequest(ctx.Request().WithContext(ctxValue)) - if provider != "github" && provider != "gitea" { + if provider != "github" && provider != "gitea" && provider != "openid-connect" { return errorRes(400, "Unsupported provider", nil) } diff --git a/internal/web/run.go b/internal/web/run.go index 0d92733..6319047 100644 --- a/internal/web/run.go +++ b/internal/web/run.go @@ -260,6 +260,7 @@ func dataInit(next echo.HandlerFunc) echo.HandlerFunc { setData(ctx, "githubOauth", config.C.GithubClientKey != "" && config.C.GithubSecret != "") setData(ctx, "giteaOauth", config.C.GiteaClientKey != "" && config.C.GiteaSecret != "") + setData(ctx, "oidcOauth", config.C.OIDCClientKey != "" && config.C.OIDCSecret != "" && config.C.OIDCDiscoveryUrl != "") return next(ctx) } diff --git a/templates/pages/admin_config.html b/templates/pages/admin_config.html index d04432b..ac77610 100644 --- a/templates/pages/admin_config.html +++ b/templates/pages/admin_config.html @@ -58,6 +58,9 @@
Gitea client Key
{{ .c.GiteaClientKey }}
Gitea Secret
{{ .c.GiteaSecret }}
Gitea URL
{{ .c.GiteaUrl }}
+
OIDC client Key
{{ .c.OIDCClientKey }}
+
OIDC Secret
{{ .c.OIDCSecret }}
+
OIDC Discovery URL
{{ .c.OIDCDiscoveryUrl }}
diff --git a/templates/pages/auth_form.html b/templates/pages/auth_form.html index b0a8b5d..3ca81c5 100644 --- a/templates/pages/auth_form.html +++ b/templates/pages/auth_form.html @@ -51,7 +51,7 @@ {{ .csrfHtml }} {{ end }} - {{ if or .githubOauth .giteaOauth }} + {{ if or .githubOauth .giteaOauth .oidcOauth }} {{ if not .disableForm }}
{{ end }}
diff --git a/templates/pages/settings.html b/templates/pages/settings.html index 18d8fd5..0a7d31f 100644 --- a/templates/pages/settings.html +++ b/templates/pages/settings.html @@ -7,7 +7,7 @@
-
+

@@ -27,7 +27,7 @@

- {{ if or .githubOauth .giteaOauth }} + {{ if or .githubOauth .giteaOauth .oidcOauth }}

@@ -60,6 +60,18 @@ {{ end }} {{ end }} + {{ if .oidcOauth }} + {{ if .userLogged.OIDCID }} + + Unlink OpenID account + + {{ else }} + + Link OpenID account + + {{ end }} + {{ end }}

From 4d0b75ed0e17a5e5c189822dd9fd9397a8e35537 Mon Sep 17 00:00:00 2001 From: Gustavo Maronato Date: Fri, 15 Sep 2023 19:11:33 -0300 Subject: [PATCH 2/4] fix wrong config key for discovery-url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c392224..c514d02 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ Opengist can be configured to use OAuth to authenticate users, with GitHub, Gite oidc.client-key: oidc.secret: # Discovery endpoint of the OpenID provider - oidc.url: http://auth.example.com/.well-known/openid-configuration + oidc.discovery-url: http://auth.example.com/.well-known/openid-configuration ``` From 933ba2da0d502ea2dfb048bdddbeebac624af8bf Mon Sep 17 00:00:00 2001 From: Gustavo Maronato Date: Fri, 15 Sep 2023 19:48:09 -0300 Subject: [PATCH 3/4] errors should not be capitalized --- internal/web/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/web/auth.go b/internal/web/auth.go index 2ef4096..d050a97 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -216,7 +216,7 @@ func oauthCallback(ctx echo.Context) error { case "gitea": resp, err = http.Get(urlJoin(config.C.GiteaUrl, user.NickName+".keys")) case "openid-connect": - err = errors.New("Cannot get keys from OIDC provider") + err = errors.New("cannot get keys from OIDC provider") } if err == nil {