mirror of
https://github.com/thomiceli/opengist
synced 2024-11-08 12:55:50 +01:00
Implement OIDC auth (#98)
This commit is contained in:
commit
35297a287a
23
README.md
23
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
|
||||
@ -114,7 +114,7 @@ You would only need to specify the configuration options you want to change —
|
||||
<summary>Configuration option list</summary>
|
||||
|
||||
| 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. |
|
||||
@ -133,6 +133,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. |
|
||||
|
||||
</details>
|
||||
|
||||
@ -221,7 +224,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.
|
||||
|
||||
<details>
|
||||
<summary>Integrate Github</summary>
|
||||
@ -249,6 +252,20 @@ Opengist can be configured to use OAuth to authenticate users, with GitHub or Gi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Integrate OpenID</summary>
|
||||
|
||||
* 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: <key>
|
||||
oidc.secret: <secret>
|
||||
# Discovery endpoint of the OpenID provider
|
||||
oidc.discovery-url: http://auth.example.com/.well-known/openid-configuration
|
||||
```
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
Opengist is licensed under the [AGPL-3.0 license](LICENSE).
|
||||
|
@ -51,7 +51,7 @@ ssh.keygen-executable: ssh-keygen
|
||||
|
||||
|
||||
# OAuth2 configuration
|
||||
# The callback/redirect URL must be http://opengist.domain/oauth/<github|gitea>/callback
|
||||
# The callback/redirect URL must be http://opengist.domain/oauth/<github|gitea|openid-connect>/callback
|
||||
|
||||
# To create a new OAuth2 application using GitHub : https://github.com/settings/applications/new
|
||||
github.client-key:
|
||||
@ -62,3 +62,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:
|
||||
|
@ -45,6 +45,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) {
|
||||
@ -222,5 +226,9 @@ func checks(c *config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := url.Parse(c.OIDCDiscoveryUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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/db"
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
@ -307,6 +307,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)
|
||||
}
|
||||
|
3
templates/pages/admin_config.html
vendored
3
templates/pages/admin_config.html
vendored
@ -55,6 +55,9 @@
|
||||
<dt>Gitea client Key</dt><dd>{{ .c.GiteaClientKey }}</dd>
|
||||
<dt>Gitea Secret</dt><dd>{{ .c.GiteaSecret }}</dd>
|
||||
<dt>Gitea URL</dt><dd>{{ .c.GiteaUrl }}</dd>
|
||||
<dt>OIDC client Key</dt><dd>{{ .c.OIDCClientKey }}</dd>
|
||||
<dt>OIDC Secret</dt><dd>{{ .c.OIDCSecret }}</dd>
|
||||
<dt>OIDC Discovery URL</dt><dd>{{ .c.OIDCDiscoveryUrl }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
|
7
templates/pages/auth_form.html
vendored
7
templates/pages/auth_form.html
vendored
@ -51,7 +51,7 @@
|
||||
{{ .csrfHtml }}
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ if or .githubOauth .giteaOauth }}
|
||||
{{ if or .githubOauth .giteaOauth .oidcOauth }}
|
||||
{{ if not .disableForm }}
|
||||
<div class="relative my-4">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
@ -71,6 +71,11 @@
|
||||
{{ .locale.Tr "auth.gitea-oauth" }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if .oidcOauth }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect" class="block w-full mb-2 text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
Continue with OpenID account
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
16
templates/pages/settings.html
vendored
16
templates/pages/settings.html
vendored
@ -7,7 +7,7 @@
|
||||
</header>
|
||||
<main>
|
||||
<div class="space-y-4">
|
||||
<div class="sm:grid {{ if or .githubOauth .giteaOauth }}grid-cols-3{{else}}grid-cols-2{{end}} gap-x-4 md:gap-x-8">
|
||||
<div class="sm:grid {{ if or .githubOauth .giteaOauth .oidcOauth }}grid-cols-3{{else}}grid-cols-2{{end}} gap-x-4 md:gap-x-8">
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300">
|
||||
@ -27,7 +27,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ if or .githubOauth .giteaOauth }}
|
||||
{{ if or .githubOauth .giteaOauth .oidcOauth }}
|
||||
<div class="w-full">
|
||||
<div class="bg-white dark:bg-gray-900 rounded-md border border-1 border-gray-200 dark:border-gray-700 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<h2 class="text-md font-bold text-slate-700 dark:text-slate-300 mb-2">
|
||||
@ -60,6 +60,18 @@
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .oidcOauth }}
|
||||
{{ if .userLogged.OIDCID }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3"
|
||||
onclick="return confirm('Are you sure you want to unlink your OpenID account? You may lose access to Opengist if it\'s your only way to log in.')">
|
||||
Unlink OpenID account
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="{{ $.c.ExternalUrl }}/oauth/openid-connect" class="block w-full text-center whitespace-nowrap text-slate-700 dark:text-slate-300{{ if .syncReposFromFS }} text-slate-500 cursor-not-allowed {{ end }}rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2.5 py-2 text-xs font-medium text-gray-700 dark:text-white shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
Link OpenID account
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user