Skip to content
This repository was archived by the owner on Mar 16, 2021. It is now read-only.

Add generic oidc #132

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ runUnitTests:
go test -v ./...

buildNodeFrontend:
cd web && yarn install
cd web && yarn install --ignore-engines
cd web && yarn build
cd web && rm build/static/**/*.map

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- Expirable Links
- URL deletion
- Multiple authorization strategies:
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta)
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta, or generic OIDC like Keycloak)
- Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/)
- Easy [ShareX](https://github.com/ShareX/ShareX) integration
- Dockerizable
Expand Down
4 changes: 4 additions & 0 deletions config/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Okta: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: 'replace me'
EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta
GenericOIDC: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: 'replace me'
EndpointURL: # (MANDATORY) Base URL, which will be auto-discovered with '.well-known/openid-configuration'
Proxy: # only relevant when using the proxy authbackend
RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set
UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header
Expand Down
6 changes: 6 additions & 0 deletions internal/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ func (h *Handler) initOAuth() {
h.providers = append(h.providers, "okta")
}

genericOIDC := util.GetConfig().GenericOIDC
if genericOIDC.Enabled() {
auth.WithAdapterWrapper(auth.NewGenericOIDCAdapter(genericOIDC.ClientID, genericOIDC.ClientSecret, genericOIDC.EndpointURL), h.engine.Group("/api/v1/auth/generic_oidc"))
h.providers = append(h.providers, "generic_oidc")
}

h.engine.POST("/api/v1/auth/check", h.handleAuthCheck)
}

Expand Down
102 changes: 102 additions & 0 deletions internal/handlers/auth/generic_oidc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package auth

import (
"context"
"strings"

"github.com/mxschmitt/golang-url-shortener/internal/util"
"github.com/sirupsen/logrus"

oidc "github.com/coreos/go-oidc"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)

type genericOIDCAdapter struct {
config *oauth2.Config
oidc *oidc.Config
provider *oidc.Provider
}

type claims struct {
PreferredUsername string `json:"sub"`
Name string `json:"name"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
ACR string `json:"acr"`
}

// NewGenericOIDCAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewGenericOIDCAdapter(clientID, clientSecret, endpointURL string) Adapter {
endpointURL = strings.TrimSuffix(endpointURL, "/")

if endpointURL == "" {
logrus.Error("Configure GenericOIDC Endpoint")
}

ctx := context.Background()
provider, err := oidc.NewProvider(ctx, endpointURL)
if err != nil {
logrus.Error("Configure GenericOIDC Endpoint: " + err.Error())
}

redirectURL := util.GetConfig().BaseURL + "/api/v1/auth/generic_oidc/callback"
// Configure an OpenID Connect aware OAuth client.
return &genericOIDCAdapter{
config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{
"profile",
"openid",
"offline_access",
},
},
oidc: &oidc.Config{
ClientID: clientID,
},
provider: provider,
}
}

func (a *genericOIDCAdapter) GetRedirectURL(state string) string {
return a.config.AuthCodeURL(state)
}

func (a *genericOIDCAdapter) GetUserData(state, code string) (*user, error) {

logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
oAuthToken, err := a.config.Exchange(context.Background(), code)
if err != nil {
return nil, errors.Wrap(err, "could not exchange code")
}

rawIDToken, ok := oAuthToken.Extra("id_token").(string)
if !ok {
return nil, errors.Wrap(err, "No id_token field in oauth2 token.")
}

idToken, err := a.provider.Verifier(a.oidc).Verify(context.Background(), rawIDToken)
if err != nil {
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
}

var oUser claims
if err = idToken.Claims(&oUser); err != nil {
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
}

return &user{
ID: string(oUser.PreferredUsername),
Name: oUser.Name,
Picture: util.GetConfig().BaseURL + "/images/generic_oidc_logo.png", // Default GenericOIDC Avatar
}, nil
}

func (a *genericOIDCAdapter) GetOAuthProviderName() string {
return "generic_oidc"
}
3 changes: 2 additions & 1 deletion internal/util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Configuration struct {
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
Okta oAuthConf `yaml:"Okta" env:"OKTA"`
GenericOIDC oAuthConf `yaml:"GenericOIDC" env:"GENERIC_OIDC"`
Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"`
Redis redisConf `yaml:"Redis" env:"REDIS"`
}
Expand All @@ -47,7 +48,7 @@ type redisConf struct {
type oAuthConf struct {
ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta and GenericOIDC
}

type proxyAuthConf struct {
Expand Down
Binary file added web/public/images/generic_oidc_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions web/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ export default class BaseComponent extends Component {
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}>
<Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta
</Button>
{info.providers.includes("generic_oidc") && <div className="ui divider"></div>}
</div>}
{info.providers.includes("generic_oidc") && <div>
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "generic_oidc")}>
<Image src='/images/generic_oidc_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with OpenID Connect
</Button>
</div>}
{info.providers.includes("microsoft") && <div>
<div className="ui divider"></div>
Expand Down