From 63d502fce14fc55dbd7b14ab61b66ebaa3bb6dee Mon Sep 17 00:00:00 2001 From: sliptripfall <7700957+sliptripfall@users.noreply.github.com> Date: Wed, 20 Mar 2019 02:28:01 -0500 Subject: [PATCH] Okta Integration - Bring Okta OAuth as an available option (#128) - Edited package.json for react bug + https://github.com/ReactTraining/react-router/issues/6630 --- README.md | 2 +- config/example.yaml | 10 ++-- internal/handlers/auth.go | 5 ++ internal/handlers/auth/okta.go | 84 ++++++++++++++++++++++++++++++++ internal/util/config.go | 5 +- web/package.json | 3 ++ web/public/images/okta_logo.png | Bin 0 -> 1314 bytes web/src/index.js | 6 +++ 8 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 internal/handlers/auth/okta.go create mode 100644 web/public/images/okta_logo.png diff --git a/README.md b/README.md index d0dcfdf..6155860 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - Expirable Links - URL deletion - Multiple authorization strategies: - - Local authorization via OAuth 2.0 (Google, GitHub and Microsoft) + - Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta) - Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/) - Easy [ShareX](https://github.com/ShareX/ShareX) integration - Dockerizable diff --git a/config/example.yaml b/config/example.yaml index 7d7caef..70da763 100644 --- a/config/example.yaml +++ b/config/example.yaml @@ -1,5 +1,5 @@ ListenAddr: ':8080' # Consists of 'IP:Port', e.g. ':8080' listens on any IP and on Port 8080 -BaseURL: 'http://localhost:3000' # Origin URL, required for the authentication via OAuth callback +BaseURL: 'http://localhost:8080' # Origin URL, required for the authentication via OAuth callback DisplayURL: '' # (OPTIONAL) Display URL, how the apication will present itself in the UI - if not set, defaults to BaseURL Backend: boltdb # Can be 'boltdb' or 'redis' DataDir: ./data # Contains: the database and the private key @@ -10,14 +10,18 @@ ShortedIDLength: 10 # Length of the random generated ID which is used for new sh AuthBackend: oauth # Can be 'oauth' or 'proxy' Google: # only relevant when using the oauth authbackend ClientID: replace me - ClientSecret: replace me + ClientSecret: 'replace me' GitHub: # only relevant when using the oauth authbackend ClientID: replace me - ClientSecret: replace me + ClientSecret: 'replace me' EndpointURL: # (OPTIONAL) URL for custom endpoint (currently only for github); e.g. 'https://github.mydomain.com' Microsoft: # only relevant when using the oauth authbackend ClientID: replace me ClientSecret: 'replace me' +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 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 diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 839dc88..d5c34a4 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -41,6 +41,11 @@ func (h *Handler) initOAuth() { auth.WithAdapterWrapper(auth.NewMicrosoftAdapter(microsoft.ClientID, microsoft.ClientSecret), h.engine.Group("/api/v1/auth/microsoft")) h.providers = append(h.providers, "microsoft") } + okta := util.GetConfig().Okta + if okta.Enabled() { + auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta")) + h.providers = append(h.providers, "okta") + } h.engine.POST("/api/v1/auth/check", h.handleAuthCheck) } diff --git a/internal/handlers/auth/okta.go b/internal/handlers/auth/okta.go new file mode 100644 index 0000000..6a88fd5 --- /dev/null +++ b/internal/handlers/auth/okta.go @@ -0,0 +1,84 @@ +package auth + +import ( + "context" + "encoding/json" + "net/url" + "strings" + + "github.com/mxschmitt/golang-url-shortener/internal/util" + "github.com/sirupsen/logrus" + + "github.com/pkg/errors" + "golang.org/x/oauth2" +) + +type oktaAdapter struct { + config *oauth2.Config +} + +// NewOktaAdapter creates an oAuth adapter out of the credentials and the baseURL +func NewOktaAdapter(clientID, clientSecret, endpointURL string) Adapter { + + if endpointURL == "" { + logrus.Error("Configure Okta Endpoint") + } + + return &oktaAdapter{&oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/okta/callback", + Scopes: []string{ + "profile", + "openid", + "offline_access", + }, + Endpoint: oauth2.Endpoint{ + AuthURL: endpointURL + "/v1/authorize", + TokenURL: endpointURL + "/v1/token", + }, + }} +} + +func (a *oktaAdapter) GetRedirectURL(state string) string { + return a.config.AuthCodeURL(state) +} + +func (a *oktaAdapter) 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") + } + if util.GetConfig().Okta.EndpointURL == "" { + logrus.Error("Okta EndpointURL is Empty") + } + oktaUrl, err := url.Parse(util.GetConfig().Okta.EndpointURL) + if err != nil { + return nil, errors.Wrap(err, "could not parse Okta EndpointURL") + } + oktaBaseURL := strings.Replace(oktaUrl.String(), oktaUrl.RequestURI(), "", 1) + oAuthUserInfoReq, err := a.config.Client(context.Background(), oAuthToken).Get(oktaBaseURL + "/api/v1/users/me") + if err != nil { + return nil, errors.Wrap(err, "could not get user data") + } + defer oAuthUserInfoReq.Body.Close() + var oUser struct { + ID int `json:"sub"` + // Custom URL property for user Avatar can go here + Name string `json:"name"` + } + if err = json.NewDecoder(oAuthUserInfoReq.Body).Decode(&oUser); err != nil { + return nil, errors.Wrap(err, "decoding user info failed") + } + return &user{ + ID: string(oUser.ID), + Name: oUser.Name, + Picture: util.GetConfig().BaseURL + "/images/okta_logo.png", // Default Okta Avatar + }, nil +} + +func (a *oktaAdapter) GetOAuthProviderName() string { + return "okta" +} diff --git a/internal/util/config.go b/internal/util/config.go index f014366..cf4677d 100644 --- a/internal/util/config.go +++ b/internal/util/config.go @@ -28,6 +28,7 @@ type Configuration struct { Google oAuthConf `yaml:"Google" env:"GOOGLE"` GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"` Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"` + Okta oAuthConf `yaml:"Okta" env:"OKTA"` Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"` Redis redisConf `yaml:"Redis" env:"REDIS"` } @@ -46,7 +47,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 only GitHub + EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta } type proxyAuthConf struct { @@ -58,7 +59,7 @@ type proxyAuthConf struct { // Config contains the default values var Config = Configuration{ ListenAddr: ":8080", - BaseURL: "http://localhost:3000", + BaseURL: "http://localhost:8080", DisplayURL: "", DataDir: "data", Backend: "boltdb", diff --git a/web/package.json b/web/package.json index 3005cdd..b3b1872 100644 --- a/web/package.json +++ b/web/package.json @@ -35,6 +35,9 @@ "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, + "resolutions": { + "react-router": "4.3.1" + }, "browserslist": [ ">0.2%", "not dead", diff --git a/web/public/images/okta_logo.png b/web/public/images/okta_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c058652d0c2cca4a956a8f67f12051bcee2ec179 GIT binary patch literal 1314 zcmV+-1>O3IP)500001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1h+{ z990y@&z;##leE~h(g!PQD-~-WiYWw1*pD=zsYrb&BGf*KZK16nNLnzsn{76mN|B;s z#R^g=l!}H@D%vV(O}dlaKovjgiVsp+RO&~gBC)C2?9BCl_nu64Gn-`M>RWzrXYM)o z?z#W_JxdELFi!-(%Sc<+I=mW@>ZOz)L?e-(iUn0*4GpbFtOt-CcO2(zYczUYmrP;s z+r`Rc?i50Nh!isn7=|Vbv8~a^6Vo1uJc-}xSLXK@W=kxi~ zx}ZA3q0sO|KHrQ{Sr0)sOv8AVi)IGHc|1Eje3H%og-kYk5=-G-HeI|ULu4XJot=Z& z`C5t(uB6!D-FUB{L|XmU4-S&2?Mhw4*m;u9ZgQM8SYyjDkTtLuBfpj$`$;m)9K@z5(LZB+tjQ|G#6PJc zX&*+I7$P;Ww=&W(kzORZ=nQpRLH16^vlRS`5rMrBu}$T)qXbd$xKiD3r-DqdRR(sH zS~q3*6VqkghkiVV-1JsIf@>Q&vI*xeOq7PF~PxT zh=32W#mH0C5PXG=a(Y^i;CV_LF74Q1rQ7~zOj8J_@DjF*xMDIFFPcHq>_o%MaZ0|; zZdC)jMXY2?BaGA3(K5?fB#v{2kt?tRm$RYfM|Gqj5D2`iJsMYxuheqoY?4w;P?_j?;JxyQjpoG&lFbIEoOR zwNkETHxPf}q1Ffkxq@zIqcW0K73)nR=q8@y+8`>_)iq)U_F$@B!~OR@yUW*%?Kpe! z8o?#@jP?L~{C7M@w82QZgL9RUjyeKL8G-LN&J%9!C-54z3xxyWaJZno)5Ju=;ZZE! zPYogNU>DI#523R7n5{`RQT&41>Rw`l`|x~J8&M6iy+9=a@EJUWK5u6t^D31=&bl>CLcDAizPC1#}gKnr87j8%5BDT2K?(n!+Qd zBB_nvBf?v3m9hy+`>;Gd$JD90--=XzFbZ*)lA&+dniYRy6t*2h;P==nM^@bB%J~k- ziC%5ajejw{8^c@GuDDu`Q$slq>s zLKAg|b|HPiHRpv#47Qw_g7QT;6J0Gf9SC2i-qy>wB(G7j?QbsegJ-HeVzBG3+P2-4 z9vE2E-{0>~ln=7mtWfdrA-)Xp*PxxtT;$@NCMngeTQ7paYqfQC8QdLVT~Hmk_cqto z*JmJN7$z=Xn)}}Yj^`h3Dep)2X$U(e9p_hFQXLqLuMq1PvTCch;(yeoV!;9n{A-B* Y0r9n^u4&sb4gdfE07*qoM6N<$f~rGjO8@`> literal 0 HcmV?d00001 diff --git a/web/src/index.js b/web/src/index.js index 8d65322..0543a4d 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -131,6 +131,12 @@ export default class BaseComponent extends Component { + {info.providers.includes("okta") &&
} + } + {info.providers.includes("okta") &&
+
} {info.providers.includes("microsoft") &&