Merge pull request #398 from openziti/oauth-testing

OAuth for Public Frontends (#45, #404)
This commit is contained in:
Michael Quigley 2023-10-06 13:37:02 -04:00 committed by GitHub
commit 859b2d7824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 2282 additions and 78 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
*.db
automated-release-build
etc/dev.yml
etc/dev-metrics.yml
etc/dev-frontend.yml
# Dependencies
/node_modules/

View File

@ -1,3 +1,9 @@
# v0.4.7
FEATURE: OAuth authentication with the ability to restrict authenticated users to specified domains for `zrok share public`. Supports both Google and GitHub authentication in this version. More authentication providers, and extensibility to come in future `zrok` releases. See the OAuth configuration guide at `docs/guides/self-hosting/oauth/configuring-oauth.md` for details (https://github.com/openziti/zrok/issues/45, https://github.com/openziti/zrok/issues/404)
CHANGE: `--basic-auth` realm now presented as the share token rather than as `zrok` in `publicProxy` frontend implementation
# v0.4.6
FEATURE: New `--backend-mode caddy`, which pre-processes a `Caddyfile` allowing a `bind` statement to work like this: `bind {{ .ZrokBindAddress }}`. Allows development of complicated API gateways and multi-backend shares, while maintaining the simple, ephemeral sharing model provided by `zrok` (https://github.com/openziti/zrok/issues/391)

View File

@ -83,7 +83,7 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
req := &sdk.ShareRequest{
BackendMode: sdk.BackendMode(cmd.backendMode),
ShareMode: shareMode,
Auth: cmd.basicAuth,
BasicAuth: cmd.basicAuth,
Target: target,
}
if shareMode == sdk.PublicShareMode {

View File

@ -99,7 +99,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
req := &sdk.ShareRequest{
BackendMode: sdk.BackendMode(cmd.backendMode),
ShareMode: sdk.PrivateShareMode,
Auth: cmd.basicAuth,
BasicAuth: cmd.basicAuth,
Target: target,
}
shr, err := sdk.CreateShare(root, req)

View File

@ -15,6 +15,7 @@ import (
"os/signal"
"strings"
"syscall"
"time"
)
func init() {
@ -22,12 +23,15 @@ func init() {
}
type sharePublicCommand struct {
basicAuth []string
frontendSelection []string
backendMode string
headless bool
insecure bool
cmd *cobra.Command
basicAuth []string
frontendSelection []string
backendMode string
headless bool
insecure bool
oauthProvider string
oauthEmailDomains []string
oauthCheckInterval time.Duration
cmd *cobra.Command
}
func newSharePublicCommand() *sharePublicCommand {
@ -37,11 +41,17 @@ func newSharePublicCommand() *sharePublicCommand {
Args: cobra.ExactArgs(1),
}
command := &sharePublicCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]")
cmd.Flags().StringArrayVar(&command.oauthEmailDomains, "oauth-email-domains", []string{}, "Allow only these email domains to authenticate via OAuth")
cmd.Flags().DurationVar(&command.oauthCheckInterval, "oauth-check-interval", 3*time.Hour, "Maximum lifetime for OAuth authentication; reauthenticate after expiry")
cmd.MarkFlagsMutuallyExclusive("basic-auth", "oauth-provider")
cmd.Run = command.run
return command
}
@ -95,9 +105,14 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
BackendMode: sdk.BackendMode(cmd.backendMode),
ShareMode: sdk.PublicShareMode,
Frontends: cmd.frontendSelection,
Auth: cmd.basicAuth,
BasicAuth: cmd.basicAuth,
Target: target,
}
if cmd.oauthProvider != "" {
req.OauthProvider = cmd.oauthProvider
req.OauthEmailDomains = cmd.oauthEmailDomains
req.OauthAuthorizationCheckInterval = cmd.oauthCheckInterval
}
shr, err := sdk.CreateShare(root, req)
if err != nil {
if !panicInstead {

View File

@ -20,10 +20,12 @@ import (
"github.com/pkg/errors"
)
var cfg *config.Config
var str *store.Store
var idb influxdb2.Client
var limitsAgent *limits.Agent
var (
cfg *config.Config
str *store.Store
idb influxdb2.Client
limitsAgent *limits.Agent
)
func Run(inCfg *config.Config) error {
cfg = inCfg

View File

@ -14,11 +14,24 @@ func newPrivateResourceAllocator() *privateResourceAllocator {
}
func (a *privateResourceAllocator) allocate(envZId, shrToken string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) {
var authUsers []*sdk.AuthUser
var authUsers []*sdk.AuthUserConfig
for _, authUser := range params.Body.AuthUsers {
authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password})
authUsers = append(authUsers, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password})
}
cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge)
authScheme, err := sdk.ParseAuthScheme(params.Body.AuthScheme)
if err != nil {
return "", nil, err
}
options := &zrokEdgeSdk.FrontendOptions{
AuthScheme: authScheme,
BasicAuthUsers: authUsers,
Oauth: &sdk.OauthConfig{
Provider: params.Body.OauthProvider,
EmailDomains: params.Body.OauthEmailDomains,
AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval,
},
}
cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, options, edge)
if err != nil {
return "", nil, err
}

View File

@ -14,11 +14,24 @@ func newPublicResourceAllocator() *publicResourceAllocator {
}
func (a *publicResourceAllocator) allocate(envZId, shrToken string, frontendZIds, frontendTemplates []string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) {
var authUsers []*sdk.AuthUser
var authUsers []*sdk.AuthUserConfig
for _, authUser := range params.Body.AuthUsers {
authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password})
authUsers = append(authUsers, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password})
}
cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge)
authScheme, err := sdk.ParseAuthScheme(params.Body.AuthScheme)
if err != nil {
return "", nil, err
}
options := &zrokEdgeSdk.FrontendOptions{
AuthScheme: authScheme,
BasicAuthUsers: authUsers,
Oauth: &sdk.OauthConfig{
Provider: params.Body.OauthProvider,
EmailDomains: params.Body.OauthEmailDomains,
AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval,
},
}
cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, options, edge)
if err != nil {
return "", nil, err
}

View File

@ -11,18 +11,27 @@ import (
"time"
)
func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, authUsers []*sdk.AuthUser, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) {
authScheme, err := sdk.ParseAuthScheme(authSchemeStr)
if err != nil {
return "", err
}
cfg := &sdk.ProxyConfig{
AuthScheme: authScheme,
type FrontendOptions struct {
AuthScheme sdk.AuthScheme
BasicAuthUsers []*sdk.AuthUserConfig
Oauth *sdk.OauthConfig
}
func CreateConfig(cfgTypeZId, envZId, shrToken string, options *FrontendOptions, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) {
cfg := &sdk.FrontendConfig{
AuthScheme: options.AuthScheme,
}
if cfg.AuthScheme == sdk.Basic {
cfg.BasicAuth = &sdk.BasicAuth{}
for _, authUser := range authUsers {
cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &sdk.AuthUser{Username: authUser.Username, Password: authUser.Password})
cfg.BasicAuth = &sdk.BasicAuthConfig{}
for _, authUser := range options.BasicAuthUsers {
cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &sdk.AuthUserConfig{Username: authUser.Username, Password: authUser.Password})
}
}
if cfg.AuthScheme == sdk.Oauth && options.Oauth != nil {
cfg.OauthAuth = &sdk.OauthConfig{
Provider: options.Oauth.Provider,
EmailDomains: options.Oauth.EmailDomains,
AuthorizationCheckInterval: options.Oauth.AuthorizationCheckInterval,
}
}
cfgCrt := &rest_model.ConfigCreate{

View File

@ -0,0 +1,7 @@
{
"label": "OAuth",
"position": 70,
"link": {
"type": "generated-index"
}
}

View File

@ -0,0 +1,154 @@
# OAuth Public Frontend Configuration
As of `v0.4.7`, `zrok` includes OAuth integration for both Google and GitHub for `zrok access public` public frontends.
This integration allows you to create public shares and request that the public frontend authenticate your users against either the Google or GitHub OAuth endpoints (using the user's Google or GitHub accounts). Additionally, you can restrict the email address domain associated with the count to a list of domains that you provide when you create the share.
This is a first step towards a more comprehensive portfolio of user authentication strategies in future `zrok` releases.
## Planning for the OAuth Frontend
The current implementation of the OAuth public frontend uses a HTTP listener to handle redirects from OAuth providers. You'll need to configure a DNS name and a port for this listener that is accessible by your end users. We'll refer to this listener as the "OAuth frontend" in this guide.
We'll use the public DNS address of the OAuth frontend when creating the Google and GitHub OAuth clients below. This address is typically configured into these clients as the "redirect URL" where these clients will send the authenticated users after authentication.
The `zrok` OAuth frontend will capture the successful authentication and forward the user back to their original destination.
## Configuring a Google OAuth Client ID
### OAuth Content Screen
Before you can configure an OAuth Client ID in Google Cloud, you have to configure the "OAuth content screen".
In the Google Cloud console, navigate to: `APIs & Services > Credentials > OAuth content screen`
![](images/google_oauth_content_screen_2.png)
Here you can give your `zrok` public frontend an identity and branding to match your deployment.
![](images/google_oauth_content_screen_3.png)
Describe what domains are authorized to access your public frontend and establish contact information.
![](images/google_oauth_content_screen_4.png)
Add a non-sensitive scope for `../auth/userinfo.email`. This is important as it allows the `zrok` OAuth frontend to receive the email address of the authenticated user.
![](images/google_oauth_content_screen_5.png)
![](images/google_oauth_content_screen_6.png)
Now your OAuth content screen is configured.
### Create the OAuth 2.0 Client ID
Next we create the OAuth Client ID for your public frontend.
In the Google Cloud Console, navigate to: `APIs & Services > Credentials > + Create Credentials`
![](images/google_create_credentials_1.png)
Select `OAuth client ID` from the `+ Create Credentials` dropdown.
![](images/google_create_credentials_2.png)
Application type is `Web Application`.
![](images/google_create_credentials_3.png)
The most important bit here is the "Authorized redirect URIs". You're going to want to put a URL here that matches the `zrok` OAuth frontend address that you configured at the start of this guide, but at the end of the URL you're going to append `/google/oauth` to the URL.
![](images/google_create_credentials_4.png)
Save the client ID and the client secret. You'll configure these into your `frontend.yml`.
With this your Google OAuth client should be configured and ready.
## Configuring a GitHub Client ID
Register a new OAuth application through the GitHub settings for the account that owns the application.
Navigate to:`Settings > Developer Settings > OAuth Apps > Register a new application`
![](images/github_create_oauth_application_1.png)
![](images/github_create_oauth_application_2.png)
The "Authorized callback URL" should be configured to match the OAuth frontend address you configured at the start of this guide, with `/github/oauth` appended to the end.
![](images/github_create_oauth_application_3.png)
Create a new client secret.
![](images/github_create_oauth_application_4.png)
Save the client ID and the client secret. You'll configure these into your `frontend.yml`.
## Configuring your Public Frontend
The public frontend configuration includes a new `oauth` section:
```yaml
oauth:
redirect_host: oauth.zrok.io
redirect_port: 28080
redirect_http_only: false
hash_key: "<yourRandomHashKey>"
providers:
- name: google
client_id: <client-id>
client_secret: <client-secret>
- name: github
client_id: <client-id>
client_secret: <client-secret>
```
The `redirect_host` and `redirect_port` value should correspond with the DNS hostname and port configured as your OAuth frontend.
The `redirect_http_only` is useful in development environments where your OAuth frontend is not running behind an HTTPS reverse proxy. Should not be enabled in production environments!
`hash_key` is a unique string for your installation that is used to secure the authentication payloads for your public frontend.
`providers` is a list of configured providers for this public frontend. The current implementation supports `google` and `github` as options.
Both the `google` and `github` providers accept a `client_id` and `client_secret` parameter. These values are provided when you configure the OAuth clients at Google or GitHub.
## Enabling OAuth on a Public Share
With your public frontend configured to support OAuth, you can test this by creating a public share. There are new command line options to support this:
```
$ zrok share public
Error: accepts 1 arg(s), received 0
Usage:
zrok share public <target> [flags]
Flags:
-b, --backend-mode string The backend mode {proxy, web, caddy} (default "proxy")
--basic-auth stringArray Basic authentication users (<username:password>,...)
--frontends stringArray Selected frontends to use for the share (default [public])
--headless Disable TUI and run headless
-h, --help help for public
--insecure Enable insecure TLS certificate validation for <target>
--oauth-check-interval duration Maximum lifetime for OAuth authentication; reauthenticate after expiry (default 3h0m0s)
--oauth-email-domains stringArray Allow only these email domains to authenticate via OAuth
--oauth-provider string Enable OAuth provider [google, github]
Global Flags:
-p, --panic Panic instead of showing pretty errors
-v, --verbose Enable verbose logging
```
The `--oauth-provider` flag enables OAuth for the share using the specified provider.
The `--oauth-email-domains` flag accepts a comma-separated list of authenticated email address domains that are allowed to access the share.
The `--oauth-check-interval` flag specifies how frequently the authentication must be checked.
An example public share:
```
$ zrok share public --backend-mode web --oauth-provider github --oauth-email-domains zrok.io ~/public
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -1,14 +1,43 @@
package publicProxy
import (
"context"
"fmt"
"github.com/michaelquigley/cf"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
zhttp "github.com/zitadel/oidc/v2/pkg/http"
"strings"
)
type Config struct {
Identity string
Address string
HostMatch string
Oauth *OauthConfig
}
type OauthConfig struct {
RedirectHost string
RedirectPort int
RedirectHttpOnly bool
HashKey string `cf:"+secret"`
Providers []*OauthProviderConfig
}
func (oc *OauthConfig) GetProvider(name string) *OauthProviderConfig {
for _, provider := range oc.Providers {
if provider.Name == name {
return provider
}
}
return nil
}
type OauthProviderConfig struct {
Name string
ClientId string
ClientSecret string `cf:"+secret"`
}
func DefaultConfig() *Config {
@ -24,3 +53,18 @@ func (c *Config) Load(path string) error {
}
return nil
}
func configureOauthHandlers(ctx context.Context, cfg *Config, tls bool) error {
if cfg.Oauth == nil {
logrus.Info("no oauth configuration; skipping oauth handler startup")
return nil
}
if err := configureGoogleOauth(cfg.Oauth, tls); err != nil {
return err
}
if err := configureGithubOauth(cfg.Oauth, tls); err != nil {
return err
}
zhttp.StartServer(ctx, fmt.Sprintf("%s:%d", strings.Split(cfg.Address, ":")[0], cfg.Oauth.RedirectPort))
return nil
}

View File

@ -0,0 +1,186 @@
package publicProxy
import (
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/zitadel/oidc/v2/pkg/client/rp"
zhttp "github.com/zitadel/oidc/v2/pkg/http"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2"
githubOAuth "golang.org/x/oauth2/github"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func configureGithubOauth(cfg *OauthConfig, tls bool) error {
scheme := "http"
if tls {
scheme = "https"
}
providerCfg := cfg.GetProvider("github")
if providerCfg == nil {
logrus.Info("unable to find provider config for github; skipping")
return nil
}
clientID := providerCfg.ClientId
callbackPath := "/github/oauth"
redirectUrl := fmt.Sprintf("%s://%s", scheme, cfg.RedirectHost)
rpConfig := &oauth2.Config{
ClientID: clientID,
ClientSecret: providerCfg.ClientSecret,
RedirectURL: fmt.Sprintf("%v:%v%v", redirectUrl, cfg.RedirectPort, callbackPath),
Scopes: []string{"user:email"},
Endpoint: githubOAuth.Endpoint,
}
hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKey))
if err != nil {
return err
}
if n != len(cfg.HashKey) {
return errors.New("short hash")
}
key := hash.Sum(nil)
u, err := url.Parse(redirectUrl)
if err != nil {
logrus.Errorf("unable to parse redirect url: %v", err)
return err
}
parts := strings.Split(u.Hostname(), ".")
domain := parts[len(parts)-2] + "." + parts[len(parts)-1]
cookieHandler := zhttp.NewCookieHandler(key, key, zhttp.WithUnsecure(), zhttp.WithDomain(domain))
options := []rp.Option{
rp.WithCookieHandler(cookieHandler),
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
//rp.WithPKCE(cookieHandler), //Github currently doesn't support pkce. Update when that changes.
}
relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...)
if err != nil {
return err
}
type IntermediateJWT struct {
State string `json:"state"`
Host string `json:"host"`
AuthorizationCheckInterval string `json:"authorizationCheckInterval"`
jwt.RegisteredClaims
}
type githubUserResp struct {
Email string
Primary bool
Verified bool
Visibility string
}
authHandlerWithQueryState := func(party rp.RelyingParty) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
if err != nil {
logrus.Errorf("Unable to unescape target host: %v", err)
}
rp.AuthURLHandler(func() string {
id := uuid.New().String()
t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{
id,
host,
r.URL.Query().Get("checkInterval"),
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "zrok",
Subject: "intermediate_token",
ID: id,
},
})
s, err := t.SignedString(key)
if err != nil {
logrus.Errorf("Unable to sign intermediate JWT: %v", err)
}
return s
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
}
}
http.Handle("/github/login", authHandlerWithQueryState(relyingParty))
getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) {
parsedUrl, err := url.Parse("https://api.github.com/user/emails")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req := &http.Request{
Method: http.MethodGet,
URL: parsedUrl,
Header: make(http.Header),
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokens.AccessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
logrus.Error("Error getting user info from github: " + err.Error() + "\n")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer func() {
_ = resp.Body.Close()
}()
response, err := io.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var rDat []githubUserResp
err = json.Unmarshal(response, &rDat)
if err != nil {
logrus.Errorf("Error unmarshalling google oauth response: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
primaryEmail := ""
for _, email := range rDat {
if email.Primary {
primaryEmail = email.Email
break
}
}
token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) {
return key, nil
})
if err != nil {
http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError)
return
}
authCheckInterval := 3 * time.Hour
i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval)
if err != nil {
logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err)
} else {
authCheckInterval = i
}
SetZrokCookie(w, domain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key)
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
}
http.Handle(callbackPath, rp.CodeExchangeHandler(getEmail, relyingParty))
return nil
}

View File

@ -0,0 +1,166 @@
package publicProxy
import (
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"github.com/zitadel/oidc/v2/pkg/client/rp"
zhttp "github.com/zitadel/oidc/v2/pkg/http"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/oauth2"
googleOauth "golang.org/x/oauth2/google"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
scheme := "http"
if tls {
scheme = "https"
}
providerCfg := cfg.GetProvider("google")
if providerCfg == nil {
logrus.Info("unable to find provider config for google. Skipping.")
return nil
}
clientID := providerCfg.ClientId
callbackPath := "/google/oauth"
redirectUrl := fmt.Sprintf("%s://%s", scheme, cfg.RedirectHost)
rpConfig := &oauth2.Config{
ClientID: clientID,
ClientSecret: providerCfg.ClientSecret,
RedirectURL: fmt.Sprintf("%v:%v%v", redirectUrl, cfg.RedirectPort, callbackPath),
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: googleOauth.Endpoint,
}
hash := md5.New()
n, err := hash.Write([]byte(cfg.HashKey))
if err != nil {
return err
}
if n != len(cfg.HashKey) {
return errors.New("short hash")
}
key := hash.Sum(nil)
u, err := url.Parse(redirectUrl)
if err != nil {
logrus.Errorf("unable to parse redirect url: %v", err)
return err
}
parts := strings.Split(u.Hostname(), ".")
domain := parts[len(parts)-2] + "." + parts[len(parts)-1]
cookieHandler := zhttp.NewCookieHandler(key, key, zhttp.WithUnsecure(), zhttp.WithDomain(domain))
options := []rp.Option{
rp.WithCookieHandler(cookieHandler),
rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)),
rp.WithPKCE(cookieHandler),
}
relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...)
if err != nil {
return err
}
type IntermediateJWT struct {
State string `json:"state"`
Host string `json:"host"`
AuthorizationCheckInterval string `json:"authorizationCheckInterval"`
jwt.RegisteredClaims
}
type googleOauthEmailResp struct {
Email string
}
authHandlerWithQueryState := func(party rp.RelyingParty) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
if err != nil {
logrus.Errorf("Unable to unescape target host: %v", err)
}
rp.AuthURLHandler(func() string {
id := uuid.New().String()
t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{
id,
host,
r.URL.Query().Get("checkInterval"),
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "zrok",
Subject: "intermediate_token",
ID: id,
},
})
s, err := t.SignedString(key)
if err != nil {
logrus.Errorf("Unable to sign intermediate JWT: %v", err)
}
return s
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
}
}
http.Handle("/google/login", authHandlerWithQueryState(relyingParty))
getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) {
resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken))
if err != nil {
logrus.Error("Error getting user info from google: " + err.Error() + "\n")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer func() {
_ = resp.Body.Close()
}()
response, err := io.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("Error reading response body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
logrus.Infof("Response from google userinfo endpoint: %s", string(response))
rDat := googleOauthEmailResp{}
err = json.Unmarshal(response, &rDat)
if err != nil {
logrus.Errorf("Error unmarshalling google oauth response: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) {
return key, nil
})
if err != nil {
http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError)
return
}
authCheckInterval := 3 * time.Hour
i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval)
if err != nil {
logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err)
} else {
authCheckInterval = i
}
SetZrokCookie(w, domain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key)
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
}
http.Handle(callbackPath, rp.CodeExchangeHandler(getEmail, relyingParty))
return nil
}

View File

@ -2,11 +2,14 @@ package publicProxy
import (
"context"
"crypto/md5"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/openziti/sdk-golang/ziti"
"github.com/openziti/zrok/endpoints"
"github.com/openziti/zrok/endpoints/publicProxy/healthUi"
"github.com/openziti/zrok/endpoints/publicProxy/notFoundUi"
"github.com/openziti/zrok/endpoints/publicProxy/unauthorizedUi"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/sdk"
"github.com/openziti/zrok/util"
@ -17,20 +20,34 @@ import (
"net/http/httputil"
"net/url"
"strings"
"time"
)
type httpFrontend struct {
type HttpFrontend struct {
cfg *Config
zCtx ziti.Context
handler http.Handler
}
func NewHTTP(cfg *Config) (*httpFrontend, error) {
env, err := environment.LoadRoot()
func NewHTTP(cfg *Config) (*HttpFrontend, error) {
var key []byte
if cfg.Oauth != nil {
hash := md5.New()
n, err := hash.Write([]byte(cfg.Oauth.HashKey))
if err != nil {
return nil, err
}
if n != len(cfg.Oauth.HashKey) {
return nil, errors.New("short hash")
}
key = hash.Sum(nil)
}
root, err := environment.LoadRoot()
if err != nil {
return nil, errors.Wrap(err, "error loading environment root")
}
zCfgPath, err := env.ZitiIdentityNamed(cfg.Identity)
zCfgPath, err := root.ZitiIdentityNamed(cfg.Identity)
if err != nil {
return nil, errors.Wrapf(err, "error getting ziti identity '%v' from environment", cfg.Identity)
}
@ -52,26 +69,28 @@ func NewHTTP(cfg *Config) (*httpFrontend, error) {
return nil, err
}
proxy.Transport = zTransport
handler := authHandler(util.NewProxyHandler(proxy), "zrok", cfg, zCtx)
return &httpFrontend{
if err := configureOauthHandlers(context.Background(), cfg, false); err != nil {
return nil, err
}
handler := authHandler(util.NewProxyHandler(proxy), cfg, key, zCtx)
return &HttpFrontend{
cfg: cfg,
zCtx: zCtx,
handler: handler,
}, nil
}
func (self *httpFrontend) Run() error {
return http.ListenAndServe(self.cfg.Address, self.handler)
func (f *HttpFrontend) Run() error {
return http.ListenAndServe(f.cfg.Address, f.handler)
}
type zitiDialContext struct {
ctx ziti.Context
}
func (self *zitiDialContext) Dial(_ context.Context, _ string, addr string) (net.Conn, error) {
func (c *zitiDialContext) Dial(_ context.Context, _ string, addr string) (net.Conn, error) {
shrToken := strings.Split(addr, ":")[0] // ignore :port (we get passed 'host:port')
conn, err := self.ctx.Dial(shrToken)
conn, err := c.ctx.Dial(shrToken)
if err != nil {
return conn, err
}
@ -128,9 +147,9 @@ func hostTargetReverseProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProx
return &httputil.ReverseProxy{Director: director}
}
func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Context) http.HandlerFunc {
func authHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
shrToken := resolveService(cfg.HostMatch, r.Host)
shrToken := resolveService(pcfg.HostMatch, r.Host)
if shrToken != "" {
if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found {
if cfg, found := svc.Config[sdk.ZrokProxyConfig]; found {
@ -145,7 +164,7 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte
logrus.Debugf("auth scheme basic '%v", shrToken)
inUser, inPass, ok := r.BasicAuth()
if !ok {
writeUnauthorizedResponse(w, realm)
basicAuthRequired(w, shrToken)
return
}
authed := false
@ -179,15 +198,96 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte
}
if !authed {
writeUnauthorizedResponse(w, realm)
basicAuthRequired(w, shrToken)
return
}
handler.ServeHTTP(w, r)
case string(sdk.Oauth):
logrus.Debugf("auth scheme oauth '%v'", shrToken)
if oauthCfg, found := cfg["oauth"]; found {
if provider, found := oauthCfg.(map[string]interface{})["provider"]; found {
var authCheckInterval time.Duration
if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found {
logrus.Errorf("Missing authorization check interval in share config. Defaulting to 3 hours")
authCheckInterval = 3 * time.Hour
} else {
i, err := time.ParseDuration(checkInterval.(string))
if err != nil {
logrus.Errorf("unable to parse authorization check interval in share config (%v). Defaulting to 3 hours", checkInterval)
authCheckInterval = 3 * time.Hour
} else {
authCheckInterval = i
}
}
target := fmt.Sprintf("%s%s", r.Host, r.URL.Path)
cookie, err := r.Cookie("zrok-access")
if err != nil {
logrus.Errorf("unable to get 'zrok-access' cookie: %v", err)
oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval)
return
}
tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) {
if pcfg.Oauth == nil {
return nil, fmt.Errorf("missing oauth configuration for access point; unable to parse jwt")
}
return key, nil
})
if err != nil {
logrus.Errorf("unable to parse jwt: %v", err)
oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval)
return
}
claims := tkn.Claims.(*ZrokClaims)
if claims.Provider != provider {
logrus.Error("provider mismatch; restarting auth flow")
oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval)
return
}
if claims.AuthorizationCheckInterval != authCheckInterval {
logrus.Error("authorization check interval mismatch; restarting auth flow")
oauthLoginRequired(w, r, shrToken, pcfg, provider.(string), target, authCheckInterval)
return
}
if validDomains, found := oauthCfg.(map[string]interface{})["email_domains"]; found {
if castedDomains, ok := validDomains.([]interface{}); !ok {
logrus.Error("invalid email domain format")
return
} else {
if len(castedDomains) > 0 {
found := false
for _, domain := range castedDomains {
if strings.HasSuffix(claims.Email, domain.(string)) {
found = true
break
}
}
if !found {
logrus.Warnf("invalid email domain")
unauthorizedUi.WriteUnauthorized(w)
return
}
}
}
}
handler.ServeHTTP(w, r)
return
} else {
logrus.Warnf("%v -> no provider for '%v'", r.RemoteAddr, provider)
notFoundUi.WriteNotFound(w)
}
} else {
logrus.Warnf("%v -> no oauth cfg for '%v'", r.RemoteAddr, shrToken)
notFoundUi.WriteNotFound(w)
}
default:
logrus.Infof("invalid auth scheme '%v'", scheme)
writeUnauthorizedResponse(w, realm)
basicAuthRequired(w, shrToken)
return
}
} else {
@ -209,14 +309,53 @@ func authHandler(handler http.Handler, realm string, cfg *Config, ctx ziti.Conte
}
}
func writeUnauthorizedResponse(w http.ResponseWriter, realm string) {
type ZrokClaims struct {
Email string `json:"email"`
AccessToken string `json:"accessToken"`
Provider string `json:"provider"`
AuthorizationCheckInterval time.Duration `json:"authorizationCheckInterval"`
jwt.RegisteredClaims
}
func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider string, checkInterval time.Duration, key []byte) {
tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, ZrokClaims{
Email: email,
AccessToken: accessToken,
Provider: provider,
AuthorizationCheckInterval: checkInterval,
})
sTkn, err := tkn.SignedString(key)
if err != nil {
http.Error(w, fmt.Sprintf("after signing cookie token: %v", err.Error()), http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: "zrok-access",
Value: sTkn,
MaxAge: int(checkInterval.Seconds()),
Domain: domain,
Path: "/",
Expires: time.Now().Add(checkInterval),
//Secure: true, //When tls gets added have this be configured on if tls
})
}
func basicAuthRequired(w http.ResponseWriter, realm string) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("No Authorization\n"))
_, _ = w.Write([]byte("No Authorization\n"))
}
func oauthLoginRequired(w http.ResponseWriter, r *http.Request, shrToken string, pcfg *Config, provider, target string, authCheckInterval time.Duration) {
scheme := "https"
if pcfg.Oauth != nil && pcfg.Oauth.RedirectHttpOnly {
scheme = "http"
}
http.Redirect(w, r, fmt.Sprintf("%s://%s.%s:%d/%s/login?targethost=%s&checkInterval=%s", scheme, shrToken, pcfg.Oauth.RedirectHost, pcfg.Oauth.RedirectPort, provider, url.QueryEscape(target), authCheckInterval.String()), http.StatusFound)
}
func resolveService(hostMatch string, host string) string {
logrus.Debugf("host = '%v'", host)
if hostMatch == "" || strings.Contains(host, hostMatch) {
tokens := strings.Split(host, ".")
if len(tokens) > 0 {

View File

@ -0,0 +1,6 @@
package unauthorizedUi
import "embed"
//go:embed index.html
var FS embed.FS

View File

@ -0,0 +1,21 @@
package unauthorizedUi
import (
"github.com/sirupsen/logrus"
"net/http"
)
func WriteUnauthorized(w http.ResponseWriter) {
if data, err := FS.ReadFile("index.html"); err == nil {
w.WriteHeader(http.StatusNotFound)
n, err := w.Write(data)
if n != len(data) {
logrus.Errorf("short write")
return
}
if err != nil {
logrus.Error(err)
return
}
}
}

View File

@ -0,0 +1,400 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#000000"/>
<meta name="description" content="zrok ui"/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Russo+One&display=swap" rel="stylesheet">
<title>zrok</title>
<style>
body {
margin: 0;
padding: 25;
font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Russo One', sans-serif;
}
table {
table-layout: fixed;
width: 100%;
}
td {
vertical-align: top;
}
td:last-child {
width: 80%;
overflow: clip;
}
td:first-child {
width: 20%;
overflow: auto;
}
#banner {
background-color: #3b2693;
text-align: center;
padding: 25px;
color: white;
display: flex;
flex-direction: column;
align-items: center;
height: 600px;
justify-content: center;
}
#banner h1 {
font-size: 64pt;
}
#container {
display: flex;
align-items: center;
justify-content: center;
}
#info {
width: 920px;
text-align: center;
}
#info h1 {
font-size: 96pt;
}
</style>
</head>
<body>
<div id="root">
<div id="banner">
<svg id="ziggy" viewBox="0 0 21.9 37.6" style="width: 200px; height: 343.38px;">
<style type="text/css">
.st0 {
fill: #FFFFFF;
}
.st1 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #FFFFFF;
}
.st2 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #A3A3A3;
}
.st3 {
fill: #00001F;
}
.st4 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #B3B3B3;
}
.st5 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #E6E6E6;
}
.st6 {
fill: #FFFFFF;
stroke: #000000;
stroke-width: 0.3;
stroke-miterlimit: 10;
}
.st7 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #FF0000;
}
.st8 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #00FF00;
}
.st9 {
fill-rule: evenodd;
clip-rule: evenodd;
fill: #06B806;
}
.st10 {
fill-rule: evenodd;
clip-rule: evenodd;
}
.st11 {
fill: #B3B3B3;
}
.st12 {
opacity: 0.25;
}
.st13 {
fill: #FEFCF9;
}
.st14 {
fill: none;
stroke: #FFFFFF;
stroke-width: 0.4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
</style>
<rect x="5.5" y="14.9" class="st0" width="10.7" height="5.7"/>
<polygon class="st0" points="16.9,35.4 17.3,35 17.2,34.5 17.1,34.2 16.2,26.5 6.1,26.6 5.1,34.2 4.9,34.8 5.4,35.2 7.3,35.1
8.4,35.3 9,35.2 9.3,34.8 9.1,34.3 10.8,28.6 11.5,28.6 11.5,29.1 13,34.3 12.9,34.8 13.2,35.2 14,35.6 "/>
<g>
<g>
<path class="st1" d="M16.4,16.1c0.3,0.1,2.8,0.8,3.4,1.7c0.5,0.9,2,4.8,2,5.8c0,0.1,0,2.6-0.2,3.3c-0.1,0.3-1.7,0.2-2.2,0.1,c-0.2,0,0.1-1.4,0-2.2c-0.1-1.6-0.5-2.6-0.6-2.4c-0.2,0.6-2.1,1.6-2.1,1.6"/>
<path class="st2" d="M21.7,26.4c0,0.2,0,0.3-0.1,0.4c-0.1,0.3-1.7,0.2-2.2,0.1c-0.2,0,0.1-1.4,0-2.2c-0.1-1.6-0.5-2.6-0.6-2.4
c-0.2,0.6-1.6,2.1-1.6,2.1l-0.3,0.1c-0.1-0.1,0.1-0.8,0.1-0.9c-0.4-0.8,1.5-2.6,1.7-2.9c0.3-0.3,0.5,0.1,0.5,0.7
c0,0.5,0,0.6,0,0.6c0.9,3.7,0.5,4.5,0.5,4.5s0.2,0.1,0.9,0c0.7,0,0.9-0.2,0.9-0.2L21.7,26.4z"/>
<path class="st3" d="M16.4,24.8c-1.6-2.7,2-0.6,2.2-1.8c0,0,0,0,0,0c0-0.5,0.1-1.1,0.2-1.5c0-0.2,0.1-0.4,0.1-0.4
c0-0.1,0.1-0.1,0.1-0.1s0.1,0.1,0.1,0.1c0,0,0,0.2-0.1,0.4c0,0.2-0.1,0.4-0.1,0.6c0.1,0.1,0.2,0.3,0.2,0.4
c0.1,0.3,0.3,0.9,0.4,1.7c0,0.2,0,0.3,0,0.5c0,0.4,0,0.9,0,1.3c0,0.4,0,0.8,0,0.8c0.3,0.1,0.8,0.1,1.4,0.1c0.3,0,0.6,0,0.6-0.1
c0.2-0.6,0.2-2.7,0.2-3.1c0,0,0-0.1,0-0.1c0-0.4-0.3-1.4-0.6-2.4c-0.5-1.4-1.1-2.8-1.3-3.3c-0.2-0.3-0.6-0.6-1.1-0.8
c-0.9-0.5-2-0.8-2.2-0.8 M16.5,16c0.2,0,1.3,0.4,2.3,0.8c0.5,0.3,1,0.6,1.2,0.9c0.3,0.5,0.9,2,1.4,3.4c0.4,1.1,0.6,2.1,0.6,2.5
c0,0,0,0,0,0.1c0,0.4,0,2.6-0.2,3.2c-0.1,0.2-0.4,0.3-0.8,0.3c-0.5,0-1.2,0-1.5-0.1c-0.2,0-0.2-0.5-0.1-1.1c0-0.4,0.1-0.9,0-1.3
c-0.1-1-0.2-1.7-0.4-2.1c0,0.1-0.8,0-0.8,0.1 M17.4,23.3l0.8-0.3c-0.1,1.2-1.5,2-1.5,2.2"/>
<path class="st1"
d="M21.5,27L21.5,27c-0.5,0.1-1.5,0.1-2,0h-0.1v1.3c0,0.4,0.3,0.8,0.8,0.8h0.6c0.4,0,0.8-0.3,0.8-0.8V27z"/>
<path class="st2" d="M21.5,27L21.5,27c-0.5,0.1-1.5,0.1-2,0h-0.1v1.3c0,0.2,0.3,0.4,0.4,0.6c0-0.1,0-0.2,0-0.2v-1.2H20
c0.4,0.1,1,0.1,1.6,0V27z"/>
<path class="st3" d="M21.7,26.8V27v1.3c0,0.2-0.1,0.5-0.3,0.6c-0.2,0.2-0.4,0.3-0.6,0.3h-0.6c-0.2,0-0.5-0.1-0.6-0.3
c-0.2-0.2-0.3-0.4-0.3-0.6V27v-0.2h0.2h0.1h0l0,0c0.3,0,0.7,0.1,1.1,0.1c0.3,0,0.6,0,0.8-0.1l0,0h0h0H21.7z M20.6,27.2
c-0.4,0-0.8,0-1-0.1v1.1c0,0.2,0.1,0.3,0.2,0.4c0.1,0.1,0.3,0.2,0.4,0.2h0.6c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.3,0.2-0.4v-1.1
C21.2,27.2,20.9,27.2,20.6,27.2L20.6,27.2z"/>
</g>
<g>
<path class="st1" d="M5.5,16.2c-0.3,0.1-2.8,0.8-3.4,1.7c-0.5,0.9-2,4.8-2,5.8c0,0.1,0,2.6,0.2,3.3C0.4,27.2,2,27.2,2.5,27
c0.2,0-0.1-1.4,0-2.2c0.1-1.6,0.5-2.6,0.6-2.4C3.3,23,5.2,24,5.2,24"/>
<path class="st2" d="M0.3,26.5c0,0.2,0,0.3,0.1,0.4C0.4,27.2,2,27.2,2.5,27c0.2,0-0.1-1.4,0-2.2c0.1-1.6,0.5-2.6,0.6-2.4
c0.2,0.6,1.6,2.1,1.6,2.1L5,24.7c0.1-0.1-0.1-0.8-0.1-0.9c0.4-0.8-1.5-2.6-1.7-2.9c-0.3-0.3-0.5,0.1-0.5,0.7c0,0.5,0,0.6,0,0.6
c-0.9,3.7-0.5,4.5-0.5,4.5s-0.2,0.1-0.9,0c-0.7,0-0.9-0.2-0.9-0.2L0.3,26.5z"/>
<path class="st3" d="M5.5,16.3c-0.2,0-1.3,0.3-2.2,0.8c-0.5,0.2-0.9,0.5-1.1,0.8c-0.3,0.5-0.9,2-1.3,3.3c-0.4,1-0.6,2-0.6,2.4
c0,0,0,0,0,0.1c0,0.4,0,2.6,0.2,3.1c0,0.1,0.3,0.1,0.6,0.1c0.5,0,1.1,0,1.4-0.1c0,0,0-0.4,0-0.8c0-0.4-0.1-0.9,0-1.3
c0-0.2,0-0.3,0-0.5c0.1-0.8,0.2-1.4,0.4-1.7c0-0.1,0.1-0.3,0.2-0.4c0-0.2-0.1-0.4-0.1-0.6c0-0.2-0.1-0.4-0.1-0.4
C2.8,21.1,2.9,21,3,21c0.1,0,0.1,0.1,0.1,0.1c0,0,0,0.2,0.1,0.4c0.1,0.4,0.1,1,0.2,1.5c0,0,0,0,0,0c0.2,1.3,3.8-0.8,2.2,1.8
M3.8,22.8c0-0.1-0.8,0-0.8-0.1c-0.1,0.4-0.3,1.1-0.4,2.1c0,0.4,0,0.9,0,1.3c0,0.6,0.1,1-0.1,1.1c-0.3,0.1-1,0.1-1.5,0.1
c-0.4,0-0.8-0.1-0.8-0.3C0,26.4,0,24.2,0,23.8c0,0,0-0.1,0-0.1c0-0.4,0.3-1.4,0.6-2.5c0.5-1.4,1.1-2.9,1.4-3.4
c0.2-0.3,0.6-0.7,1.2-0.9c0.9-0.5,2.1-0.8,2.3-0.8 M5.2,25.3c0-0.2-1.3-1-1.5-2.2l0.8,0.3"/>
<path class="st1" d="M0.4,27.1L0.4,27.1c0.5,0.1,1.5,0.1,2,0h0.1v1.3c0,0.4-0.3,0.8-0.8,0.8H1.2c-0.4,0-0.8-0.3-0.8-0.8V27.1z"/>
<path class="st2" d="M0.4,27.1L0.4,27.1c0.5,0.1,1.5,0.1,2,0h0.1v1.3c0,0.2-0.3,0.4-0.4,0.6c0-0.1,0-0.2,0-0.2v-1.2H2
c-0.4,0.1-1,0.1-1.6,0V27.1z"/>
<path class="st3" d="M0.4,26.9L0.4,26.9L0.4,26.9L0.4,26.9C0.6,27,1,27,1.3,27c0.4,0,0.8,0,1.1-0.1l0,0h0h0.1h0.2v0.2v1.3
c0,0.2-0.1,0.5-0.3,0.6c-0.2,0.2-0.4,0.3-0.6,0.3H1.2c-0.2,0-0.5-0.1-0.6-0.3c-0.2-0.2-0.3-0.4-0.3-0.6v-1.3v-0.2H0.4z M1.3,27.3
c-0.3,0-0.5,0-0.7,0v1.1c0,0.2,0.1,0.3,0.2,0.4C0.8,28.9,1,29,1.2,29h0.6c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.3,0.2-0.4v-1.1
C2,27.3,1.7,27.3,1.3,27.3L1.3,27.3z"/>
</g>
<g>
<path class="st2" d="M5.1,34.2h4.1l1.6-5.6c0.1,0,0.4,0,0.5,0l1.7,5.6h4.1l0-0.3c-0.1,0-0.1,0-0.2,0c-0.4,0.1-3.2,0-3.4,0
c-0.2,0-1.6-4.9-1.6-5.1c0-0.2,0.3-0.1,0.6-0.6c0.3-0.5-0.4-0.5-0.4-0.5c-1,0.4-2,0-2,0c-0.9,0.6,0.2,1,0.2,1.1
c0.1,0.1-1.3,5-1.5,5.1c-0.2,0.1-3.4,0-3.4,0s-0.1,0-0.2,0L5.1,34.2z"/>
<path class="st3" d="M16.4,27.9 M6.1,27.3L6.1,27.3L6.1,27.3l-0.9,6.9h3.8l1.5-5.6c-0.4-0.1-0.5-0.3-0.5-0.3
c-0.1-0.1-0.1-0.1,0-0.2c0.1-0.1,0.1-0.1,0.2,0c0,0,0.3,0.3,0.8,0.2c0.6,0,0.8-0.3,0.8-0.3c0.1-0.1,0.1,0,0.2,0
c0.1,0.1,0.1,0.1,0,0.2c0,0-0.2,0.2-0.6,0.3l1.6,5.6h3.8l-0.7-7 M16.4,27.3l0.8,7.1l0,0.2h-0.2H13h-0.1l0-0.1l-1.6-5.7
c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0l-1.6,5.7l0,0.1H9.2H5.1H4.9l0-0.2l0.9-7.1"/>
<path class="st4" d="M5.4,35.2h3.5c0.2,0,0.5-0.2,0.5-0.4l0,0c0-0.2-0.2-0.4-0.5-0.4H5.4c-0.2,0-0.5,0.2-0.5,0.4l0,0
C4.9,35,5.1,35.2,5.4,35.2L5.4,35.2z"/>
<path class="st3" d="M5.4,35.1h3.5C9,35.1,9,35,9.1,35c0.1-0.1,0.1-0.1,0.1-0.2c0-0.1,0-0.2-0.1-0.2C9,34.5,9,34.4,8.9,34.4H5.4
c-0.1,0-0.2,0-0.2,0.1c-0.1,0.1-0.1,0.1-0.1,0.2c0,0.1,0,0.2,0.1,0.2C5.2,35,5.3,35.1,5.4,35.1L5.4,35.1z M8.9,35.3H5.4
c-0.2,0-0.3-0.1-0.4-0.2c-0.1-0.1-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h3.5c0.2,0,0.3,0.1,0.4,0.2
c0.1,0.1,0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4C9.2,35.3,9,35.3,8.9,35.3L8.9,35.3z"/>
<path class="st1" d="M4.8,37.5h4.4v-1.3C9.2,35.5,8.7,35,8,35H6.1c-0.7,0-1.3,0.6-1.3,1.3V37.5z"/>
<path class="st3" d="M5,37.4h4.1v-1.1c0-0.3-0.1-0.6-0.3-0.8c-0.2-0.2-0.5-0.3-0.8-0.3H6.1c-0.3,0-0.6,0.1-0.8,0.3
C5.1,35.6,5,35.9,5,36.2V37.4z M9.2,37.6H4.8H4.7v-0.1v-1.3c0-0.4,0.2-0.7,0.4-1c0.3-0.3,0.6-0.4,1-0.4H8c0.4,0,0.7,0.2,1,0.4
c0.3,0.3,0.4,0.6,0.4,1v1.3v0.1H9.2z"/>
<path class="st4" d="M13.4,35.2h3.5c0.2,0,0.5-0.2,0.5-0.4l0,0c0-0.2-0.2-0.4-0.5-0.4h-3.5c-0.2,0-0.5,0.2-0.5,0.4l0,0
C12.9,35,13.1,35.2,13.4,35.2L13.4,35.2z"/>
<path class="st3" d="M13.4,35.1h3.5c0.1,0,0.2,0,0.2-0.1c0.1-0.1,0.1-0.1,0.1-0.2c0-0.1,0-0.2-0.1-0.2c-0.1-0.1-0.1-0.1-0.2-0.1
h-3.5c-0.1,0-0.2,0-0.2,0.1c-0.1,0.1-0.1,0.1-0.1,0.2c0,0.1,0,0.2,0.1,0.2C13.2,35,13.3,35.1,13.4,35.1L13.4,35.1z M16.9,35.3
h-3.5c-0.2,0-0.3-0.1-0.4-0.2c-0.1-0.1-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h3.5c0.2,0,0.3,0.1,0.4,0.2
c0.1,0.1,0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4C17.2,35.3,17,35.3,16.9,35.3L16.9,35.3z"/>
<path class="st1" d="M13,37.5h4.4v-1.3c0-0.7-0.6-1.3-1.3-1.3h-1.8c-0.7,0-1.3,0.6-1.3,1.3V37.5z"/>
<path class="st4" d="M17.4,37.5H13v-1.3c0-0.3,0.1-0.7,0.4-0.9c0.1,0,0.2,0,0.2,0c0,0,0,0.2,0,0.4v1.3h3.8V37.5z"/>
<path class="st3" d="M13.1,37.4h4.1v-1.1c0-0.3-0.1-0.6-0.3-0.8c-0.2-0.2-0.5-0.3-0.8-0.3h-1.8c-0.3,0-0.6,0.1-0.8,0.3
c-0.2,0.2-0.3,0.5-0.3,0.8V37.4z M17.4,37.6H13h-0.1v-0.1v-1.3c0-0.4,0.2-0.7,0.4-1c0.3-0.3,0.6-0.4,1-0.4h1.8
c0.4,0,0.7,0.2,1,0.4c0.3,0.3,0.4,0.6,0.4,1v1.3v0.1H17.4z"/>
<path class="st4" d="M9.1,37.4H5v-1.2c0-0.3,0.1-0.6,0.4-0.8c0.1,0,0.2,0,0.2,0c0,0,0,0.2,0,0.4v1.2h3.6V37.4z"/>
</g>
<path class="st5" d="M2.5,15.4C2.3,15.1,2,15,2.1,13.6c0.1-0.9-0.1-2.4,1-2.1l0,5.5l-1.6,2.1c0,0-0.2-2.7,0-3.2
C1.8,15.4,2.3,15.4,2.5,15.4L2.5,15.4z"/>
<path class="st4" d="M3.1,16.7l0,0.4l-1.6,2c0,0-0.1-1-0.1-1.9c0.3-0.3,0.7-0.5,1.1-0.7c0-0.1,0-0.2,0-0.4v-4.7
c0.1-0.1,0.3-0.1,0.5,0L3.1,16.7L3.1,16.7z"/>
<path class="st3" d="M2.7,15.3c0-0.1-0.1-0.1-0.1-0.1c-0.2-0.2-0.3-0.4-0.3-1.5l0,0c0-0.1,0-0.3,0-0.5c0-0.4,0-0.8,0.1-1.1
c0.1-0.2,0.2-0.4,0.5-0.4l0,5.3l-1.2,1.6c0-0.8-0.1-2.2,0-2.6c0.1-0.2,0.2-0.3,0.3-0.3c0.1,0,0.3,0,0.4,0l0.4,0L2.7,15.3z
M1.9,13.6c0,1,0.1,1.4,0.2,1.6c-0.1,0-0.1,0-0.2,0c-0.2,0.1-0.4,0.2-0.6,0.6c-0.2,0.5,0,3.3,0,3.3l0,0.5l0.3-0.4l1.6-2.1l0-0.1V17
l0-5.5l0-0.1l-0.1,0c-0.7-0.2-1,0.1-1.1,0.6c-0.1,0.3-0.1,0.8-0.1,1.2C1.9,13.3,1.9,13.4,1.9,13.6L1.9,13.6z"/>
<path class="st5" d="M6.1,10.5h-2c-0.6,0-1,0.5-1,1v5.3c0,0.6,0.5,1,1,1h2c0.6,0,1-0.5,1-1v-5.3C7.2,11,6.7,10.5,6.1,10.5L6.1,10.5
z"/>
<path class="st4" d="M3.1,16.5v0.7c0,0.6,0.5,1,1,1h2c0.6,0,1-0.5,1-1v-0.2c0,0,0,0,0.1,0v-5.2c0-0.6-0.5-1-1-1h-2
c-0.6,0-1,0.5-1,1v1.8c0.5,0.8,1.1,1.4,1.8,2C4.4,15.9,3.7,16.2,3.1,16.5L3.1,16.5z"/>
<path class="st3" d="M6.2,10.3h-2c-0.3,0-0.7,0.1-0.9,0.4C3.1,10.9,3,11.2,3,11.5v5.3c0,0.3,0.1,0.6,0.4,0.9
C3.5,17.9,3.9,18,4.2,18h2c0.3,0,0.7-0.1,0.9-0.4c0.2-0.2,0.4-0.5,0.4-0.9v-5.3c0-0.3-0.1-0.6-0.4-0.9C6.8,10.5,6.5,10.3,6.2,10.3
L6.2,10.3z M4.2,10.7h2c0.2,0,0.4,0.1,0.6,0.2C6.9,11.1,7,11.3,7,11.5v5.3c0,0.2-0.1,0.4-0.2,0.6c-0.2,0.1-0.4,0.2-0.6,0.2h-2
c-0.2,0-0.4-0.1-0.6-0.2c-0.2-0.1-0.2-0.4-0.2-0.6v-5.3c0-0.2,0.1-0.4,0.2-0.6C3.7,10.8,4,10.7,4.2,10.7L4.2,10.7z"/>
<path class="st5" d="M19.4,15.4c0.2-0.3,0.5-0.4,0.4-1.8c-0.1-0.9,0.1-2.4-1-2.1l0,5.5l1.6,2.1c0,0,0.2-2.7,0-3.2
C20.2,15.4,19.7,15.4,19.4,15.4L19.4,15.4z"/>
<path class="st4" d="M18.8,16.9l0,0.4l1.6,2c0,0,0.1-1,0.1-1.9c-0.3-0.3-0.7-0.5-1.1-0.7c0-0.1,0-0.2,0-0.4v-4.8
c-0.1-0.1-0.3-0.1-0.5,0L18.8,16.9L18.8,16.9z"/>
<path class="st3" d="M19.3,15.3c0-0.1,0.1-0.1,0.1-0.1c0.2-0.2,0.3-0.4,0.3-1.5l0,0c0-0.1,0-0.3,0-0.5c0-0.4,0-0.8-0.1-1.1
c-0.1-0.2-0.2-0.4-0.5-0.4l0,5.3l1.2,1.6c0-0.8,0.1-2.2,0-2.6c-0.1-0.2-0.2-0.3-0.3-0.3c-0.1,0-0.3,0-0.4,0l-0.4,0L19.3,15.3z
M20,13.6c0,1-0.1,1.4-0.2,1.6c0.1,0,0.1,0,0.2,0c0.2,0.1,0.4,0.2,0.6,0.6c0.2,0.5,0,3.3,0,3.3l0,0.5l-0.3-0.4l-1.6-2.1l0-0.1
l0-0.1l0-5.5l0-0.1l0.1,0c0.7-0.2,1,0.1,1.1,0.6c0.1,0.3,0.1,0.8,0.1,1.2C20,13.3,20,13.4,20,13.6L20,13.6z"/>
<path class="st5" d="M15.8,10.5h2c0.6,0,1,0.5,1,1v5.3c0,0.6-0.5,1-1,1h-2c-0.6,0-1-0.5-1-1v-5.3C14.8,11,15.2,10.5,15.8,10.5
L15.8,10.5z"/>
<path class="st4" d="M18.8,16.5v0.7c0,0.6-0.5,1-1,1h-2c-0.6,0-1-0.5-1-1v-2c0.6,0,1,0,1,0C15.9,15.3,17.4,15.8,18.8,16.5
L18.8,16.5z"/>
<path class="st3" d="M15.8,10.3h2c0.3,0,0.7,0.1,0.9,0.4c0.2,0.2,0.4,0.5,0.4,0.9v5.3c0,0.3-0.1,0.6-0.4,0.9
c-0.2,0.2-0.5,0.4-0.9,0.4h-2c-0.3,0-0.7-0.1-0.9-0.4c-0.2-0.2-0.4-0.5-0.4-0.9v-5.3c0-0.3,0.1-0.6,0.4-0.9
C15.1,10.5,15.4,10.3,15.8,10.3L15.8,10.3z M17.7,10.7h-2c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.2,0.4-0.2,0.6v5.3
c0,0.2,0.1,0.4,0.2,0.6c0.2,0.1,0.4,0.2,0.6,0.2h2c0.2,0,0.4-0.1,0.6-0.2c0.2-0.1,0.2-0.4,0.2-0.6v-5.3c0-0.2-0.1-0.4-0.2-0.6
C18.2,10.8,18,10.7,17.7,10.7L17.7,10.7z"/>
<g>
<ellipse transform="matrix(0.9999 -1.050124e-02 1.050124e-02 0.9999 -0.2761 0.1189)" class="st6" cx="11.2"
cy="26.4" rx="6.3" ry="2.7"/>
</g>
<path class="st2" d="M14.4,16.6c-0.3-0.1-0.6-0.2-0.6-0.2c-0.2,0-5.4,0-5.7,0c-0.1,0-0.5,0.1-1.1,0.3C9.2,18,12.2,18,14.4,16.6
L14.4,16.6z"/>
<path class="st6" d="M3.2,18.6l0.1,6.1c0,1.8,3.6,3.3,7.9,3.2s7.7-1.6,7.8-3.4c0-0.2,0-2.9,0-6.4l-0.1-1.3c0,1.8-3.3,0.9-7.6,0.9
s-8.1,0.9-8.1-0.9L3.2,18.6z"/>
<g>
<g>
<path class="st2" d="M15.7,16c-0.7-0.3-1.2-0.5-1.3-0.5c-0.9,1.3-0.7,7.7-0.8,7.9c-0.2,0.2-0.2,0.7-0.1,1.3
c0.1,0.6,1.5,1.1,1.8,0c0.4-1.1-0.4-1.4-0.4-1.4C14.6,20.8,15.3,16.8,15.7,16L15.7,16z"/>
<path class="st2" d="M7.5,15.6c-0.3,0.1-0.8,0.2-1.3,0.5c0.4,0.5,1.1,4.8,0.9,7.3c0,0-0.7,0.3-0.4,1.4c0.4,1.1,1.7,0.6,1.8,0
c0.1-0.6,0-1.1-0.1-1.3C8.2,23.4,8.5,16.8,7.5,15.6L7.5,15.6z"/>
<path class="st2" d="M7.6,15.6c-0.3,0.1-0.8,0.2-1.3,0.5c0.4,0.6,1.1,4.8,0.9,7.3c0,0-0.7,0.3-0.4,1.4c0.4,1.1,1.7,0.6,1.8,0
c0.1-0.6,0-1.1-0.1-1.3C8.2,23.3,8.5,16.8,7.6,15.6L7.6,15.6z"/>
<path class="st2" d="M9.4,23.6h3.2c0.5,0,0.8,0.4,0.8,0.9v1.4c0,0.5-0.4,0.9-0.8,0.9H9.4c-0.5,0-0.8-0.4-0.8-0.9v-1.4
C8.5,24,8.9,23.6,9.4,23.6L9.4,23.6z"/>
<path class="st1" d="M9.6,23.9h2.7c0.4,0,0.7,0.3,0.7,0.7v1.1c0,0.4-0.3,0.7-0.7,0.7H9.6c-0.4,0-0.7-0.3-0.7-0.7v-1.1
C8.9,24.3,9.2,23.9,9.6,23.9L9.6,23.9z"/>
<path class="st2" d="M11.5,23.9h0.3c0.5,0.1,0.9,0.5,0.9,1c0,0.6-0.5,1-1,1c-0.6,0-1-0.5-1-1C10.6,24.4,11,24,11.5,23.9
L11.5,23.9z"/>
<path class="st2" d="M10,24.2c0.4,0,0.8,0.3,0.8,0.8c0,0.4-0.3,0.8-0.8,0.8c-0.4,0-0.8-0.3-0.8-0.8C9.2,24.6,9.5,24.2,10,24.2
L10,24.2z"/>
<path class="st7" d="M11.6,24.3c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7c-0.4,0-0.7-0.3-0.7-0.7
C10.9,24.6,11.2,24.3,11.6,24.3L11.6,24.3z"/>
<path class="st3" d="M11.6,24.1c0.2,0,0.4,0.1,0.6,0.2c0.1,0.1,0.2,0.4,0.2,0.6c0,0.2-0.1,0.4-0.2,0.6c-0.1,0.1-0.4,0.2-0.6,0.2
c-0.2,0-0.4-0.1-0.6-0.2c-0.1-0.1-0.2-0.4-0.2-0.6c0-0.2,0.1-0.4,0.2-0.6C11.1,24.2,11.3,24.1,11.6,24.1L11.6,24.1z M11.9,24.6
c-0.1-0.1-0.2-0.2-0.4-0.2c-0.1,0-0.3,0.1-0.4,0.2c-0.1,0.1-0.2,0.2-0.2,0.4c0,0.1,0.1,0.3,0.2,0.4c0.1,0.1,0.2,0.2,0.4,0.2
c0.1,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.2,0.2-0.4C12.1,24.8,12,24.7,11.9,24.6L11.9,24.6z"/>
<path class="st8" d="M10,24.6c0.2,0,0.4,0.2,0.4,0.4c0,0.2-0.2,0.4-0.4,0.4c-0.2,0-0.4-0.2-0.4-0.4C9.5,24.7,9.7,24.6,10,24.6
L10,24.6z"/>
<path class="st9" d="M10.3,24.7c0.1,0.1,0.1,0.2,0.1,0.3c0,0.2-0.2,0.4-0.4,0.4c-0.2-0.1-0.3-0.2-0.2-0.2
C10,25.2,10.2,25,10.3,24.7C10.2,24.8,10.2,24.7,10.3,24.7L10.3,24.7z"/>
<path class="st3" d="M10,24.4c0.2,0,0.3,0.1,0.4,0.2c0.1,0.1,0.2,0.2,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.2,0.2-0.4,0.2
c-0.2,0-0.3-0.1-0.4-0.2S9.4,25.1,9.4,25c0-0.2,0.1-0.3,0.2-0.4C9.7,24.5,9.8,24.4,10,24.4L10,24.4z M10.2,24.8
c0,0-0.1-0.1-0.2-0.1c-0.1,0-0.1,0-0.2,0.1c0,0-0.1,0.1-0.1,0.2c0,0.1,0,0.1,0.1,0.2c0,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0,0.2-0.1
c0,0,0.1-0.1,0.1-0.2C10.2,24.9,10.2,24.8,10.2,24.8L10.2,24.8z"/>
<path class="st3" d="M9.6,23.8h2.7c0.2,0,0.5,0.1,0.6,0.3c0.2,0.2,0.3,0.4,0.3,0.6v1.1c0,0.2-0.1,0.5-0.3,0.6
c-0.2,0.2-0.4,0.3-0.6,0.3H9.6c-0.2,0-0.5-0.1-0.6-0.3c-0.2-0.2-0.3-0.4-0.3-0.6v-1.1c0-0.2,0.1-0.5,0.3-0.6
C9.1,23.9,9.4,23.8,9.6,23.8L9.6,23.8z M12.3,24.1H9.6c-0.2,0-0.3,0.1-0.4,0.2C9.1,24.4,9,24.5,9,24.7v1.1c0,0.2,0.1,0.3,0.2,0.4
c0.1,0.1,0.2,0.2,0.4,0.2h2.7c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.2,0.2-0.4v-1.1c0-0.2-0.1-0.3-0.2-0.4
C12.7,24.2,12.5,24.1,12.3,24.1L12.3,24.1z"/>
<path class="st3" d="M7.5,23.6h0.1C8,20.3,7.3,16.8,7.2,16l-0.3,0.1c0.6,1.5,0.8,4.3,0.8,6C7.6,22.6,7.6,23.2,7.5,23.6L7.5,23.6z
M7.9,23.7c0,0,0.1,0,0.1,0.1c0.1,0.1,0.1,0.2,0.1,0.3v0.4c0,0.1-0.1,0.2-0.1,0.3c-0.1,0.1-0.2,0.1-0.3,0.1H7.3
c-0.1,0-0.2-0.1-0.3-0.1c-0.1-0.1-0.1-0.2-0.1-0.3v-0.4c0-0.1,0.1-0.2,0.1-0.3c0.1-0.1,0.1-0.1,0.2-0.1c0-0.1,0-0.1,0-0.3
c0-0.3,0-0.7,0-1.1c0-1.8-0.1-4.6-0.8-6L6.4,16l0.2-0.1l0.6-0.2l0.2-0.1l0,0.2C7.4,15.8,8.3,19.8,7.9,23.7L7.9,23.7z M7.8,23.9
L7.8,23.9l-0.4,0c0,0-0.1,0-0.1,0c0,0,0,0.1,0,0.1v0.4c0,0,0,0.1,0,0.1c0,0,0.1,0,0.1,0h0.4c0,0,0.1,0,0.1,0c0,0,0-0.1,0-0.1
v-0.4C7.9,24.1,7.9,24,7.8,23.9C7.8,24,7.8,24,7.8,23.9L7.8,23.9z"/>
<path class="st3" d="M7.6,23.6h0.1C8,20.2,7.4,16.8,7.2,16l-0.3,0.1c0.6,1.5,0.8,4.3,0.8,6C7.6,22.6,7.6,23.2,7.6,23.6L7.6,23.6z
M8,23.7c0,0,0.1,0,0.1,0.1c0.1,0.1,0.1,0.2,0.1,0.3v0.4c0,0.1-0.1,0.2-0.1,0.3c-0.1,0.1-0.2,0.1-0.3,0.1H7.4
c-0.1,0-0.2-0.1-0.3-0.1c-0.1-0.1-0.1-0.2-0.1-0.3v-0.4c0-0.1,0.1-0.2,0.1-0.3c0.1-0.1,0.1-0.1,0.2-0.1c0-0.1,0-0.1,0-0.3
c0-0.3,0-0.7,0-1.1c0-1.8-0.1-4.6-0.8-6l-0.1-0.1l0.2-0.1l0.6-0.2l0.2-0.1l0,0.2C7.5,15.8,8.4,19.8,8,23.7L8,23.7z M7.8,23.9
L7.8,23.9l-0.4,0c0,0-0.1,0-0.1,0c0,0,0,0.1,0,0.1v0.4c0,0,0,0.1,0,0.1c0,0,0.1,0,0.1,0h0.4c0,0,0.1,0,0.1,0c0,0,0-0.1,0-0.1
v-0.4C7.9,24,7.9,24,7.8,23.9C7.9,23.9,7.8,23.9,7.8,23.9L7.8,23.9z"/>
<path class="st3" d="M14.4,23.5h-0.1c-0.3-3.4,0.3-6.8,0.5-7.6L15,16c-0.6,1.5-0.8,4.3-0.8,6C14.3,22.4,14.3,23,14.4,23.5
L14.4,23.5z M13.9,23.5c0,0-0.1,0-0.1,0.1c-0.1,0.1-0.1,0.2-0.1,0.3v0.4c0,0.1,0.1,0.2,0.1,0.3c0.1,0.1,0.2,0.1,0.3,0.1h0.4
c0.1,0,0.2-0.1,0.3-0.1c0.1-0.1,0.1-0.2,0.1-0.3v-0.4c0-0.1-0.1-0.2-0.1-0.3c-0.1-0.1-0.1-0.1-0.2-0.1c0-0.1,0-0.1,0-0.3
c0-0.3,0-0.7,0-1.1c0-1.8,0.1-4.6,0.8-6l0.1-0.1l-0.2-0.1l-0.6-0.2l-0.2-0.1l0,0.2C14.5,15.6,13.5,19.7,13.9,23.5L13.9,23.5z
M14.1,23.8L14.1,23.8l0.4,0c0,0,0.1,0,0.1,0c0,0,0,0.1,0,0.1v0.4c0,0,0,0.1,0,0.1c0,0-0.1,0-0.1,0h-0.4c0,0-0.1,0-0.1,0
c0,0,0-0.1,0-0.1v-0.4C14,23.9,14,23.8,14.1,23.8C14.1,23.8,14.1,23.8,14.1,23.8L14.1,23.8z"/>
</g>
</g>
<path class="st4" d="M15.5,10.6h2c0.6,0,1,0.5,1,1v1.8c-0.6,0.9-1.3,1.7-2.2,2.4c-0.6,0.4,0,0-0.6,0.2l-0.3-4.3
C15.4,11.2,15,10.6,15.5,10.6L15.5,10.6z"/>
<path class="st10" d="M5.4,2.1c3.7-2.8,9.1-2.1,12,1.5c2.9,3.6,2.2,8.9-1.6,11.7c-3.7,2.8-9.1,2.1-12-1.5C1,10.2,1.7,4.9,5.4,2.1
L5.4,2.1z"/>
<path class="st1" d="M1.3,10.6l0.9,0C2,9.4,2,8.3,2.3,7.2l0,0l-1,0c-0.6,0-1,0.5-1,1l0,1.4C0.3,10.2,0.8,10.6,1.3,10.6L1.3,10.6z"/>
<path class="st1" d="M19.9,10.5l-0.9,0c0.2-1.1,0.2-2.3,0-3.4l0,0l1,0c0.6,0,1,0.5,1,1l0,1.4C20.9,10.1,20.4,10.6,19.9,10.5
L19.9,10.5z"/>
<path d="M5.3,2c1.9-1.4,4.3-2,6.5-1.7c2.2,0.3,4.4,1.4,5.8,3.3c1.5,1.9,2,4.2,1.7,6.3c-0.3,2.2-1.4,4.2-3.4,5.7
c-1.9,1.4-4.3,2-6.5,1.7c-2.2-0.3-4.4-1.4-5.8-3.3c-1.5-1.9-2-4.2-1.7-6.3C2.2,5.5,3.3,3.4,5.3,2L5.3,2z M11.7,0.7
C9.6,0.5,7.4,1,5.6,2.3C3.7,3.7,2.7,5.6,2.4,7.7c-0.3,2.1,0.3,4.2,1.7,6c1.4,1.8,3.4,2.8,5.5,3.1c2.1,0.3,4.3-0.2,6.1-1.6
c1.8-1.4,2.9-3.3,3.2-5.4c0.3-2.1-0.3-4.2-1.7-6C15.8,2,13.8,1,11.7,0.7L11.7,0.7z"/>
<path class="st2" d="M1.3,10.6l0.9,0C2,9.4,2,8.3,2.3,7.2l0,0l-0.8,0C1.4,8.1,1.2,8.9,1.3,9.9l-0.9,0c0,0-0.1,0-0.1,0.1
C0.5,10.3,0.9,10.6,1.3,10.6L1.3,10.6z"/>
<path class="st2" d="M19.9,10.5l-0.9,0c0.2-1.1,0.2-2.3,0-3.4l0,0l0.8,0C19.8,8,20,8.9,19.8,9.9l0.9,0c0,0,0.1,0,0.1,0.1
C20.7,10.3,20.3,10.5,19.9,10.5L19.9,10.5z"/>
<path class="st3" d="M1.3,10.8l0.9,0l0.3,0l-0.1-0.3C2.3,10,2.3,9.4,2.3,8.9c0-0.5,0.1-1.1,0.2-1.6l0,0l0,0l0,0l0-0.2l-0.2,0l-1,0
l0,0C0.9,7,0.6,7.1,0.3,7.3C0.1,7.6,0,7.9,0,8.2v0l0,1.4c0,0.3,0.2,0.6,0.4,0.9C0.7,10.7,1,10.8,1.3,10.8L1.3,10.8z M1.9,10.3
l-0.6,0c-0.2,0-0.4-0.1-0.6-0.2C0.6,10,0.5,9.8,0.5,9.6l0-1.4l0,0c0-0.2,0.1-0.4,0.2-0.5C0.8,7.5,1,7.4,1.2,7.4h0l0.7,0
c-0.1,0.5-0.1,1-0.2,1.5C1.8,9.4,1.9,9.8,1.9,10.3L1.9,10.3z"/>
<path class="st3" d="M19.9,10.8l-0.9,0l-0.3,0l0.1-0.3c0.1-0.5,0.2-1.1,0.1-1.6c0-0.5-0.1-1.1-0.2-1.6l0,0l0,0l0,0l0-0.2l0.2,0l1,0
h0c0.3,0,0.7,0.2,0.9,0.4c0.2,0.2,0.4,0.5,0.3,0.9v0l0,1.4c0,0.3-0.2,0.6-0.4,0.9C20.5,10.6,20.2,10.8,19.9,10.8L19.9,10.8z
M19.3,10.3l0.6,0c0.2,0,0.4-0.1,0.6-0.2c0.1-0.1,0.2-0.3,0.2-0.5l0-1.4l0,0c0-0.2-0.1-0.4-0.2-0.5c-0.1-0.1-0.3-0.2-0.5-0.2l0,0
l-0.7,0c0.1,0.5,0.1,1,0.2,1.5C19.4,9.3,19.3,9.8,19.3,10.3L19.3,10.3z"/>
<path class="st1" d="M19.1,9.8c-0.3,2.1-1.4,4.2-3.3,5.6c-3.7,2.8-9.1,2.1-12-1.5c-0.6-0.8-1.1-1.7-1.4-2.6
C5.7,16.6,14.9,18.9,19.1,9.8L19.1,9.8z"/>
<path class="st3" d="M19.4,9.8c-0.1,1.1-0.5,2.2-1.1,3.2c-0.6,1-1.3,1.8-2.3,2.5c-1.9,1.4-4.3,2-6.5,1.7c-2.2-0.3-4.4-1.4-5.8-3.3
c-0.3-0.4-0.6-0.8-0.8-1.3c-0.2-0.5-0.4-0.9-0.6-1.4L1.9,9.8l0.7,1.3c1,1.7,2.6,3.1,4.5,3.9c1.2,0.5,2.4,0.8,3.7,0.8
c1.3,0,2.5-0.3,3.7-1c1.7-0.9,3.2-2.6,4.3-5.1l0.5-1.4L19.4,9.8z M17.8,12.8c0.2-0.4,0.4-0.8,0.6-1.2c-1,1.7-2.3,2.8-3.6,3.6
c-1.3,0.7-2.6,1-4,1.1c-1.3,0-2.7-0.3-3.9-0.8c-1.2-0.5-2.4-1.3-3.3-2.3c0.1,0.2,0.3,0.4,0.4,0.6c1.4,1.8,3.4,2.8,5.5,3.1
c2.1,0.3,4.3-0.2,6.1-1.6C16.6,14.5,17.3,13.7,17.8,12.8L17.8,12.8z"/>
<path class="st1" d="M5.4,2c3.7-2.8,9.1-2.1,12,1.5c1.4,1.8,2,4,1.7,6.1c-0.8-3.3-2.3-5.1-4.3-6.1c-3.1-1.6-6.3-1.8-9.6,1
c-2.4,2-2.7,4.5-2.8,6.6C1.4,7.8,2.5,4.1,5.4,2L5.4,2z"/>
<path class="st3" d="M5.3,1.8c1.9-1.4,4.3-2,6.5-1.7c2.2,0.3,4.4,1.4,5.8,3.3c0.7,0.9,1.2,1.9,1.5,3c0.3,1.1,0.4,2.2,0.2,3.3
l-0.2,1.3l-0.3-1.3c-0.4-1.6-0.9-2.9-1.6-3.9c-0.7-1-1.6-1.6-2.5-2.1c-1.5-0.8-3-1.2-4.6-1.1C8.6,2.6,7,3.2,5.4,4.6
c-1.2,1-1.8,2.1-2.2,3.2C2.9,8.9,2.8,10,2.7,11l-0.1,1.2l-0.4-1.2c-0.5-1.7-0.5-3.5,0-5.1C2.8,4.3,3.8,2.9,5.3,1.8L5.3,1.8z
M11.7,0.5C9.6,0.3,7.4,0.8,5.6,2.1c-1.4,1-2.4,2.4-2.9,3.9C2.3,7.2,2.2,8.3,2.4,9.5c0.1-0.6,0.2-1.2,0.4-1.9
c0.4-1.2,1.1-2.3,2.3-3.4c1.7-1.4,3.3-2.1,5-2.2c1.6-0.1,3.3,0.4,4.8,1.2c1,0.5,1.9,1.3,2.7,2.3C18.1,6.2,18.6,7,18.9,8
c0-0.5-0.1-1.1-0.3-1.6c-0.3-1-0.8-1.9-1.4-2.8C15.8,1.9,13.8,0.8,11.7,0.5L11.7,0.5z"/>
<path class="st5" d="M19.2,8.8c0,0.2,0,0.5-0.1,0.7c-0.8-3.3-2.3-5.1-4.3-6.1C11.7,1.8,7.4,1.9,4.7,4.6C3.9,5.4,2.5,7.2,2.4,9.7
C2,7.2,3,4.9,5.4,3c3.7-2.8,9.1-2.1,12,1.5C18.5,5.8,19,7.3,19.2,8.8L19.2,8.8z"/>
<path class="st3" d="M19.4,8.8c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4l-0.1,1.1l-0.2-1.1C18.5,8,18,6.7,17.3,5.7
c-0.7-1-1.6-1.6-2.5-2.1c-1.6-0.8-3.5-1.2-5.3-1C7.7,2.8,6.1,3.5,4.9,4.7C4.4,5.2,3.7,6,3.2,7.1C2.9,7.8,2.6,8.7,2.6,9.7l-0.1,2.1
l-0.3-2C2,8.4,2.1,7.2,2.7,6c0.5-1.1,1.4-2.2,2.6-3.1l0,0l0,0c1.9-1.4,4.2-2,6.5-1.7c2.2,0.3,4.3,1.4,5.8,3.2l0,0l0,0
c0.5,0.6,0.9,1.3,1.2,2.1C19.1,7.3,19.3,8,19.4,8.8L19.4,8.8L19.4,8.8z M17.6,5.5c0.5,0.7,0.9,1.4,1.2,2.4
c-0.1-0.4-0.2-0.8-0.4-1.2c-0.3-0.7-0.7-1.4-1.2-2l0,0c-1.4-1.8-3.4-2.8-5.5-3.1C9.6,1.3,7.4,1.8,5.6,3.2l0,0c-1.2,0.9-2,1.9-2.5,3
c-0.2,0.5-0.4,1-0.5,1.5c0.1-0.2,0.2-0.5,0.3-0.7c0.5-1.1,1.3-2,1.8-2.5c1.3-1.3,3-2,4.8-2.2c1.9-0.2,3.9,0.2,5.5,1
C16,3.8,16.9,4.5,17.6,5.5L17.6,5.5z"/>
<path class="st5" d="M3.8,8.2c0,0,1.4-4.1,6.2-4.5c0.5-0.1,1.1,1.3,0.4,1.4c-0.7,0.2-3.6,0.6-5.4,4C4.7,9.4,3.5,8.9,3.8,8.2
L3.8,8.2z"/>
<path class="st5" d="M12.4,5.1c0.4,0,0.7-0.3,0.7-0.7c0-0.4-0.3-0.7-0.7-0.7c-0.4,0-0.7,0.3-0.7,0.7C11.7,4.8,12,5.1,12.4,5.1
L12.4,5.1z"/>
<path class="st11" d="M10.9,18.4l-1.5,2.3l0.9,0.6l0.1-0.2l0.5,2.2l1.5-2.3l-0.8-0.5l-0.1,0.2L10.9,18.4z M11.6,20.7l0.4,0.2
l-1,1.5l-0.5-2.2l-0.4,0.6l-0.4-0.2l1-1.5l0.4,2.1L11.6,20.7z"/>
<g class="st12">
<circle class="st13" cx="14.1" cy="8.9" r="1.8"/>
<circle cx="14.4" cy="8.7" r="0.7"/>
<circle class="st13" cx="7.1" cy="8.9" r="1.8"/>
<path id="XMLID_00000028285902548687721290000002641907447975500945_" class="st14" d="M10.8,14.1c-1.6,0-2.7-0.7-3.3-1.1
c-0.1-0.1-0.1-0.2,0-0.3c0.1-0.1,0.2-0.1,0.3,0c0.5,0.4,1.7,1.1,3.3,1c1.4,0,2.4-0.7,2.9-1c0.1-0.1,0.2,0,0.3,0
c0.1,0.1,0,0.2,0,0.3c-0.5,0.4-1.6,1-3.1,1.1C10.9,14.1,10.9,14.1,10.8,14.1z"/>
<circle cx="7.6" cy="8.6" r="0.7"/>
</g>
</g>
</svg>
<h1>zrok</h1>
</div>
<div id="container">
<div id="info">
<h1>Unauthorized</h1>
</div>
</div>
</div>
</body>
</html>

20
etc/frontend.yml Normal file
View File

@ -0,0 +1,20 @@
# Setting the `host_match` setting will cause a `zrok access public` to ignore `Host` headers that do not contain the
# configured string. This will allow you to let a load balancer access the frontend by IP address for health check
# purposes, and will allow `Host` headers that match the configured DNS name to be routed through `zrok`.
#
#host_match: zrok.io
# The OAuth configuration is used when enabling OAuth authentication with your public frontend.
#
#oauth:
# redirect_host: oauth.zrok.io
# redirect_port: 28080
# redirect_http_only: false
# hash_key: "<yourRandomHashKey>"
# providers:
# - name: google
# client_id: <client-id>
# client_secret: <client-secret>
# - name: github
# client_id: <client-id>
# client_secret: <client-secret>

View File

@ -1,5 +0,0 @@
# Setting the `host_match` setting will cause a `zrok access public` to ignore `Host` headers that do not contain the
# configured string. This will allow you to let a load balancer access the frontend by IP address for health check
# purposes, and will allow `Host` headers that match the configured DNS name to be routed through `zrok`.
#
host_match: zrok.io

12
go.mod
View File

@ -14,6 +14,8 @@ require (
github.com/go-openapi/strfmt v0.21.7
github.com/go-openapi/swag v0.22.4
github.com/go-openapi/validate v0.22.1
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/iancoleman/strcase v0.2.0
github.com/influxdata/influxdb-client-go/v2 v2.11.0
@ -41,14 +43,18 @@ require (
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
github.com/wneessen/go-mail v0.2.7
github.com/zitadel/oidc/v2 v2.7.0
go.uber.org/zap v1.25.0
golang.org/x/crypto v0.12.0
golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.10.0
golang.org/x/time v0.3.0
nhooyr.io/websocket v1.8.7
)
require (
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
@ -98,7 +104,6 @@ require (
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
@ -108,8 +113,9 @@ require (
github.com/google/go-tpm v0.3.3 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.12 // indirect
@ -150,6 +156,7 @@ require (
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
@ -221,6 +228,7 @@ require (
golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect

13
go.sum
View File

@ -39,7 +39,9 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
@ -640,6 +642,10 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -766,6 +772,7 @@ github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jedib0t/go-pretty/v6 v6.4.3 h1:2n9BZ0YQiXGESUSR+6FLg0WWWE80u+mIz35f0uHWcIE=
github.com/jedib0t/go-pretty/v6 v6.4.3/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
@ -985,6 +992,8 @@ github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
@ -1166,6 +1175,7 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@ -1351,6 +1361,8 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zitadel/oidc/v2 v2.7.0 h1:IGX4EDk6tegTjUSsZDWeTfLseFU0BdJ/Glf1tgys2lU=
github.com/zitadel/oidc/v2 v2.7.0/go.mod h1:zkUkVJS0sDVy9m0UA9RgO3f8i/C0rtjvXU36UJj7T+0=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -1635,6 +1647,7 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -0,0 +1,184 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewOauthAuthenticateParams creates a new OauthAuthenticateParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewOauthAuthenticateParams() *OauthAuthenticateParams {
return &OauthAuthenticateParams{
timeout: cr.DefaultTimeout,
}
}
// NewOauthAuthenticateParamsWithTimeout creates a new OauthAuthenticateParams object
// with the ability to set a timeout on a request.
func NewOauthAuthenticateParamsWithTimeout(timeout time.Duration) *OauthAuthenticateParams {
return &OauthAuthenticateParams{
timeout: timeout,
}
}
// NewOauthAuthenticateParamsWithContext creates a new OauthAuthenticateParams object
// with the ability to set a context for a request.
func NewOauthAuthenticateParamsWithContext(ctx context.Context) *OauthAuthenticateParams {
return &OauthAuthenticateParams{
Context: ctx,
}
}
// NewOauthAuthenticateParamsWithHTTPClient creates a new OauthAuthenticateParams object
// with the ability to set a custom HTTPClient for a request.
func NewOauthAuthenticateParamsWithHTTPClient(client *http.Client) *OauthAuthenticateParams {
return &OauthAuthenticateParams{
HTTPClient: client,
}
}
/*
OauthAuthenticateParams contains all the parameters to send to the API endpoint
for the oauth authenticate operation.
Typically these are written to a http.Request.
*/
type OauthAuthenticateParams struct {
// Code.
Code string
// State.
State *string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the oauth authenticate params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *OauthAuthenticateParams) WithDefaults() *OauthAuthenticateParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the oauth authenticate params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *OauthAuthenticateParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the oauth authenticate params
func (o *OauthAuthenticateParams) WithTimeout(timeout time.Duration) *OauthAuthenticateParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the oauth authenticate params
func (o *OauthAuthenticateParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the oauth authenticate params
func (o *OauthAuthenticateParams) WithContext(ctx context.Context) *OauthAuthenticateParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the oauth authenticate params
func (o *OauthAuthenticateParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the oauth authenticate params
func (o *OauthAuthenticateParams) WithHTTPClient(client *http.Client) *OauthAuthenticateParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the oauth authenticate params
func (o *OauthAuthenticateParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithCode adds the code to the oauth authenticate params
func (o *OauthAuthenticateParams) WithCode(code string) *OauthAuthenticateParams {
o.SetCode(code)
return o
}
// SetCode adds the code to the oauth authenticate params
func (o *OauthAuthenticateParams) SetCode(code string) {
o.Code = code
}
// WithState adds the state to the oauth authenticate params
func (o *OauthAuthenticateParams) WithState(state *string) *OauthAuthenticateParams {
o.SetState(state)
return o
}
// SetState adds the state to the oauth authenticate params
func (o *OauthAuthenticateParams) SetState(state *string) {
o.State = state
}
// WriteToRequest writes these params to a swagger request
func (o *OauthAuthenticateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// query param code
qrCode := o.Code
qCode := qrCode
if qCode != "" {
if err := r.SetQueryParam("code", qCode); err != nil {
return err
}
}
if o.State != nil {
// query param state
var qrState string
if o.State != nil {
qrState = *o.State
}
qState := qrState
if qState != "" {
if err := r.SetQueryParam("state", qState); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@ -0,0 +1,208 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)
// OauthAuthenticateReader is a Reader for the OauthAuthenticate structure.
type OauthAuthenticateReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *OauthAuthenticateReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewOauthAuthenticateOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 302:
result := NewOauthAuthenticateFound()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 500:
result := NewOauthAuthenticateInternalServerError()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("response status code does not match any response statuses defined for this endpoint in the swagger spec", response, response.Code())
}
}
// NewOauthAuthenticateOK creates a OauthAuthenticateOK with default headers values
func NewOauthAuthenticateOK() *OauthAuthenticateOK {
return &OauthAuthenticateOK{}
}
/*
OauthAuthenticateOK describes a response with status code 200, with default header values.
testing
*/
type OauthAuthenticateOK struct {
}
// IsSuccess returns true when this oauth authenticate o k response has a 2xx status code
func (o *OauthAuthenticateOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this oauth authenticate o k response has a 3xx status code
func (o *OauthAuthenticateOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this oauth authenticate o k response has a 4xx status code
func (o *OauthAuthenticateOK) IsClientError() bool {
return false
}
// IsServerError returns true when this oauth authenticate o k response has a 5xx status code
func (o *OauthAuthenticateOK) IsServerError() bool {
return false
}
// IsCode returns true when this oauth authenticate o k response a status code equal to that given
func (o *OauthAuthenticateOK) IsCode(code int) bool {
return code == 200
}
func (o *OauthAuthenticateOK) Error() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateOK ", 200)
}
func (o *OauthAuthenticateOK) String() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateOK ", 200)
}
func (o *OauthAuthenticateOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewOauthAuthenticateFound creates a OauthAuthenticateFound with default headers values
func NewOauthAuthenticateFound() *OauthAuthenticateFound {
return &OauthAuthenticateFound{}
}
/*
OauthAuthenticateFound describes a response with status code 302, with default header values.
redirect back to share
*/
type OauthAuthenticateFound struct {
/* Redirect URL
*/
Location string
}
// IsSuccess returns true when this oauth authenticate found response has a 2xx status code
func (o *OauthAuthenticateFound) IsSuccess() bool {
return false
}
// IsRedirect returns true when this oauth authenticate found response has a 3xx status code
func (o *OauthAuthenticateFound) IsRedirect() bool {
return true
}
// IsClientError returns true when this oauth authenticate found response has a 4xx status code
func (o *OauthAuthenticateFound) IsClientError() bool {
return false
}
// IsServerError returns true when this oauth authenticate found response has a 5xx status code
func (o *OauthAuthenticateFound) IsServerError() bool {
return false
}
// IsCode returns true when this oauth authenticate found response a status code equal to that given
func (o *OauthAuthenticateFound) IsCode(code int) bool {
return code == 302
}
func (o *OauthAuthenticateFound) Error() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateFound ", 302)
}
func (o *OauthAuthenticateFound) String() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateFound ", 302)
}
func (o *OauthAuthenticateFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// hydrates response header location
hdrLocation := response.GetHeader("location")
if hdrLocation != "" {
o.Location = hdrLocation
}
return nil
}
// NewOauthAuthenticateInternalServerError creates a OauthAuthenticateInternalServerError with default headers values
func NewOauthAuthenticateInternalServerError() *OauthAuthenticateInternalServerError {
return &OauthAuthenticateInternalServerError{}
}
/*
OauthAuthenticateInternalServerError describes a response with status code 500, with default header values.
internal server error
*/
type OauthAuthenticateInternalServerError struct {
}
// IsSuccess returns true when this oauth authenticate internal server error response has a 2xx status code
func (o *OauthAuthenticateInternalServerError) IsSuccess() bool {
return false
}
// IsRedirect returns true when this oauth authenticate internal server error response has a 3xx status code
func (o *OauthAuthenticateInternalServerError) IsRedirect() bool {
return false
}
// IsClientError returns true when this oauth authenticate internal server error response has a 4xx status code
func (o *OauthAuthenticateInternalServerError) IsClientError() bool {
return false
}
// IsServerError returns true when this oauth authenticate internal server error response has a 5xx status code
func (o *OauthAuthenticateInternalServerError) IsServerError() bool {
return true
}
// IsCode returns true when this oauth authenticate internal server error response a status code equal to that given
func (o *OauthAuthenticateInternalServerError) IsCode(code int) bool {
return code == 500
}
func (o *OauthAuthenticateInternalServerError) Error() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateInternalServerError ", 500)
}
func (o *OauthAuthenticateInternalServerError) String() string {
return fmt.Sprintf("[GET /oauth/authorize][%d] oauthAuthenticateInternalServerError ", 500)
}
func (o *OauthAuthenticateInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}

View File

@ -41,6 +41,12 @@ func (o *ShareReader) ReadResponse(response runtime.ClientResponse, consumer run
return nil, err
}
return nil, result
case 422:
result := NewShareUnprocessableEntity()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
case 500:
result := NewShareInternalServerError()
if err := result.readResponse(response, consumer, o.formats); err != nil {
@ -217,6 +223,57 @@ func (o *ShareNotFound) readResponse(response runtime.ClientResponse, consumer r
return nil
}
// NewShareUnprocessableEntity creates a ShareUnprocessableEntity with default headers values
func NewShareUnprocessableEntity() *ShareUnprocessableEntity {
return &ShareUnprocessableEntity{}
}
/*
ShareUnprocessableEntity describes a response with status code 422, with default header values.
unprocessable
*/
type ShareUnprocessableEntity struct {
}
// IsSuccess returns true when this share unprocessable entity response has a 2xx status code
func (o *ShareUnprocessableEntity) IsSuccess() bool {
return false
}
// IsRedirect returns true when this share unprocessable entity response has a 3xx status code
func (o *ShareUnprocessableEntity) IsRedirect() bool {
return false
}
// IsClientError returns true when this share unprocessable entity response has a 4xx status code
func (o *ShareUnprocessableEntity) IsClientError() bool {
return true
}
// IsServerError returns true when this share unprocessable entity response has a 5xx status code
func (o *ShareUnprocessableEntity) IsServerError() bool {
return false
}
// IsCode returns true when this share unprocessable entity response a status code equal to that given
func (o *ShareUnprocessableEntity) IsCode(code int) bool {
return code == 422
}
func (o *ShareUnprocessableEntity) Error() string {
return fmt.Sprintf("[POST /share][%d] shareUnprocessableEntity ", 422)
}
func (o *ShareUnprocessableEntity) String() string {
return fmt.Sprintf("[POST /share][%d] shareUnprocessableEntity ", 422)
}
func (o *ShareUnprocessableEntity) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
return nil
}
// NewShareInternalServerError creates a ShareInternalServerError with default headers values
func NewShareInternalServerError() *ShareInternalServerError {
return &ShareInternalServerError{}

View File

@ -40,6 +40,16 @@ type ShareRequest struct {
// frontend selection
FrontendSelection []string `json:"frontendSelection"`
// oauth authorization check interval
OauthAuthorizationCheckInterval string `json:"oauthAuthorizationCheckInterval,omitempty"`
// oauth email domains
OauthEmailDomains []string `json:"oauthEmailDomains"`
// oauth provider
// Enum: [github google]
OauthProvider string `json:"oauthProvider,omitempty"`
// reserved
Reserved bool `json:"reserved,omitempty"`
@ -60,6 +70,10 @@ func (m *ShareRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateOauthProvider(formats); err != nil {
res = append(res, err)
}
if err := m.validateShareMode(formats); err != nil {
res = append(res, err)
}
@ -147,6 +161,48 @@ func (m *ShareRequest) validateBackendMode(formats strfmt.Registry) error {
return nil
}
var shareRequestTypeOauthProviderPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["github","google"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
shareRequestTypeOauthProviderPropEnum = append(shareRequestTypeOauthProviderPropEnum, v)
}
}
const (
// ShareRequestOauthProviderGithub captures enum value "github"
ShareRequestOauthProviderGithub string = "github"
// ShareRequestOauthProviderGoogle captures enum value "google"
ShareRequestOauthProviderGoogle string = "google"
)
// prop value enum
func (m *ShareRequest) validateOauthProviderEnum(path, location string, value string) error {
if err := validate.EnumCase(path, location, value, shareRequestTypeOauthProviderPropEnum, true); err != nil {
return err
}
return nil
}
func (m *ShareRequest) validateOauthProvider(formats strfmt.Registry) error {
if swag.IsZero(m.OauthProvider) { // not required
return nil
}
// value enum
if err := m.validateOauthProviderEnum("oauthProvider", "body", m.OauthProvider); err != nil {
return err
}
return nil
}
var shareRequestTypeShareModePropEnum []interface{}
func init() {

View File

@ -865,6 +865,9 @@ func init() {
"404": {
"description": "not found"
},
"422": {
"description": "unprocessable"
},
"500": {
"description": "internal server error",
"schema": {
@ -1484,6 +1487,22 @@ func init() {
"type": "string"
}
},
"oauthAuthorizationCheckInterval": {
"type": "string"
},
"oauthEmailDomains": {
"type": "array",
"items": {
"type": "string"
}
},
"oauthProvider": {
"type": "string",
"enum": [
"github",
"google"
]
},
"reserved": {
"type": "boolean"
},
@ -2462,6 +2481,9 @@ func init() {
"404": {
"description": "not found"
},
"422": {
"description": "unprocessable"
},
"500": {
"description": "internal server error",
"schema": {
@ -3081,6 +3103,22 @@ func init() {
"type": "string"
}
},
"oauthAuthorizationCheckInterval": {
"type": "string"
},
"oauthEmailDomains": {
"type": "array",
"items": {
"type": "string"
}
},
"oauthProvider": {
"type": "string",
"enum": [
"github",
"google"
]
},
"reserved": {
"type": "boolean"
},

View File

@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// OauthAuthenticateHandlerFunc turns a function with the right signature into a oauth authenticate handler
type OauthAuthenticateHandlerFunc func(OauthAuthenticateParams) middleware.Responder
// Handle executing the request and returning a response
func (fn OauthAuthenticateHandlerFunc) Handle(params OauthAuthenticateParams) middleware.Responder {
return fn(params)
}
// OauthAuthenticateHandler interface for that can handle valid oauth authenticate params
type OauthAuthenticateHandler interface {
Handle(OauthAuthenticateParams) middleware.Responder
}
// NewOauthAuthenticate creates a new http.Handler for the oauth authenticate operation
func NewOauthAuthenticate(ctx *middleware.Context, handler OauthAuthenticateHandler) *OauthAuthenticate {
return &OauthAuthenticate{Context: ctx, Handler: handler}
}
/*
OauthAuthenticate swagger:route GET /oauth/authorize share oauthAuthenticate
OauthAuthenticate oauth authenticate API
*/
type OauthAuthenticate struct {
Context *middleware.Context
Handler OauthAuthenticateHandler
}
func (o *OauthAuthenticate) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewOauthAuthenticateParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@ -0,0 +1,109 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
// NewOauthAuthenticateParams creates a new OauthAuthenticateParams object
//
// There are no default values defined in the spec.
func NewOauthAuthenticateParams() OauthAuthenticateParams {
return OauthAuthenticateParams{}
}
// OauthAuthenticateParams contains all the bound params for the oauth authenticate operation
// typically these are obtained from a http.Request
//
// swagger:parameters oauthAuthenticate
type OauthAuthenticateParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: query
*/
Code string
/*
In: query
*/
State *string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewOauthAuthenticateParams() beforehand.
func (o *OauthAuthenticateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qCode, qhkCode, _ := qs.GetOK("code")
if err := o.bindCode(qCode, qhkCode, route.Formats); err != nil {
res = append(res, err)
}
qState, qhkState, _ := qs.GetOK("state")
if err := o.bindState(qState, qhkState, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindCode binds and validates parameter Code from query.
func (o *OauthAuthenticateParams) bindCode(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("code", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("code", "query", raw); err != nil {
return err
}
o.Code = raw
return nil
}
// bindState binds and validates parameter State from query.
func (o *OauthAuthenticateParams) bindState(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: false
// AllowEmptyValue: false
if raw == "" { // empty values pass all other validations
return nil
}
o.State = &raw
return nil
}

View File

@ -0,0 +1,109 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
)
// OauthAuthenticateOKCode is the HTTP code returned for type OauthAuthenticateOK
const OauthAuthenticateOKCode int = 200
/*
OauthAuthenticateOK testing
swagger:response oauthAuthenticateOK
*/
type OauthAuthenticateOK struct {
}
// NewOauthAuthenticateOK creates OauthAuthenticateOK with default headers values
func NewOauthAuthenticateOK() *OauthAuthenticateOK {
return &OauthAuthenticateOK{}
}
// WriteResponse to the client
func (o *OauthAuthenticateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(200)
}
// OauthAuthenticateFoundCode is the HTTP code returned for type OauthAuthenticateFound
const OauthAuthenticateFoundCode int = 302
/*
OauthAuthenticateFound redirect back to share
swagger:response oauthAuthenticateFound
*/
type OauthAuthenticateFound struct {
/*Redirect URL
*/
Location string `json:"location"`
}
// NewOauthAuthenticateFound creates OauthAuthenticateFound with default headers values
func NewOauthAuthenticateFound() *OauthAuthenticateFound {
return &OauthAuthenticateFound{}
}
// WithLocation adds the location to the oauth authenticate found response
func (o *OauthAuthenticateFound) WithLocation(location string) *OauthAuthenticateFound {
o.Location = location
return o
}
// SetLocation sets the location to the oauth authenticate found response
func (o *OauthAuthenticateFound) SetLocation(location string) {
o.Location = location
}
// WriteResponse to the client
func (o *OauthAuthenticateFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
// response header location
location := o.Location
if location != "" {
rw.Header().Set("location", location)
}
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(302)
}
// OauthAuthenticateInternalServerErrorCode is the HTTP code returned for type OauthAuthenticateInternalServerError
const OauthAuthenticateInternalServerErrorCode int = 500
/*
OauthAuthenticateInternalServerError internal server error
swagger:response oauthAuthenticateInternalServerError
*/
type OauthAuthenticateInternalServerError struct {
}
// NewOauthAuthenticateInternalServerError creates OauthAuthenticateInternalServerError with default headers values
func NewOauthAuthenticateInternalServerError() *OauthAuthenticateInternalServerError {
return &OauthAuthenticateInternalServerError{}
}
// WriteResponse to the client
func (o *OauthAuthenticateInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(500)
}

View File

@ -0,0 +1,109 @@
// Code generated by go-swagger; DO NOT EDIT.
package share
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// OauthAuthenticateURL generates an URL for the oauth authenticate operation
type OauthAuthenticateURL struct {
Code string
State *string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *OauthAuthenticateURL) WithBasePath(bp string) *OauthAuthenticateURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *OauthAuthenticateURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *OauthAuthenticateURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/oauth/authorize"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
codeQ := o.Code
if codeQ != "" {
qs.Set("code", codeQ)
}
var stateQ string
if o.State != nil {
stateQ = *o.State
}
if stateQ != "" {
qs.Set("state", stateQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *OauthAuthenticateURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *OauthAuthenticateURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *OauthAuthenticateURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on OauthAuthenticateURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on OauthAuthenticateURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *OauthAuthenticateURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@ -108,6 +108,31 @@ func (o *ShareNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.P
rw.WriteHeader(404)
}
// ShareUnprocessableEntityCode is the HTTP code returned for type ShareUnprocessableEntity
const ShareUnprocessableEntityCode int = 422
/*
ShareUnprocessableEntity unprocessable
swagger:response shareUnprocessableEntity
*/
type ShareUnprocessableEntity struct {
}
// NewShareUnprocessableEntity creates ShareUnprocessableEntity with default headers values
func NewShareUnprocessableEntity() *ShareUnprocessableEntity {
return &ShareUnprocessableEntity{}
}
// WriteResponse to the client
func (o *ShareUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
rw.WriteHeader(422)
}
// ShareInternalServerErrorCode is the HTTP code returned for type ShareInternalServerError
const ShareInternalServerErrorCode int = 500

View File

@ -4,26 +4,35 @@ import "github.com/pkg/errors"
const ZrokProxyConfig = "zrok.proxy.v1"
type ProxyConfig struct {
AuthScheme AuthScheme `json:"auth_scheme"`
BasicAuth *BasicAuth `json:"basic_auth"`
type FrontendConfig struct {
AuthScheme AuthScheme `json:"auth_scheme"`
BasicAuth *BasicAuthConfig `json:"basic_auth"`
OauthAuth *OauthConfig `json:"oauth"`
}
type BasicAuth struct {
Users []*AuthUser `json:"users"`
type BasicAuthConfig struct {
Users []*AuthUserConfig `json:"users"`
}
type AuthUser struct {
type AuthUserConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
type OauthConfig struct {
Provider string `json:"provider"`
EmailDomains []string `json:"email_domains"`
AuthorizationCheckInterval string `json:"authorization_check_interval"`
}
func ParseAuthScheme(authScheme string) (AuthScheme, error) {
switch authScheme {
case string(None):
return None, nil
case string(Basic):
return Basic, nil
case string(Oauth):
return Oauth, nil
default:
return None, errors.Errorf("unknown auth scheme '%v'", authScheme)
}

View File

@ -1,5 +1,7 @@
package sdk
import "time"
type BackendMode string
const (
@ -18,11 +20,14 @@ const (
)
type ShareRequest struct {
BackendMode BackendMode
ShareMode ShareMode
Frontends []string
Auth []string
Target string
BackendMode BackendMode
ShareMode ShareMode
Target string
Frontends []string
BasicAuth []string
OauthProvider string
OauthEmailDomains []string
OauthAuthorizationCheckInterval time.Duration
}
type Share struct {
@ -56,4 +61,5 @@ type AuthScheme string
const (
None AuthScheme = "none"
Basic AuthScheme = "basic"
Oauth AuthScheme = "oauth"
)

View File

@ -26,18 +26,22 @@ func CreateShare(root env_core.Root, request *ShareRequest) (*Share, error) {
return nil, errors.Errorf("unknown share mode '%v'", request.ShareMode)
}
if len(request.Auth) > 0 {
if len(request.BasicAuth) > 0 {
out.Body.AuthScheme = string(Basic)
for _, pair := range request.Auth {
tokens := strings.Split(pair, ":")
for _, basicAuthUser := range request.BasicAuth {
tokens := strings.Split(basicAuthUser, ":")
if len(tokens) == 2 {
out.Body.AuthUsers = append(out.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])})
} else {
return nil, errors.Errorf("invalid username:password pair '%v'", pair)
return nil, errors.Errorf("invalid username:password '%v'", basicAuthUser)
}
}
}
if request.OauthProvider != "" {
out.Body.AuthScheme = string(Oauth)
}
zrok, err := root.Client()
if err != nil {
return nil, errors.Wrap(err, "error getting zrok client")
@ -70,12 +74,15 @@ func newPrivateShare(root env_core.Root, request *ShareRequest) *share.SharePara
func newPublicShare(root env_core.Root, request *ShareRequest) *share.ShareParams {
req := share.NewShareParams()
req.Body = &rest_model_zrok.ShareRequest{
EnvZID: root.Environment().ZitiIdentity,
ShareMode: string(request.ShareMode),
FrontendSelection: request.Frontends,
BackendMode: string(request.BackendMode),
BackendProxyEndpoint: request.Target,
AuthScheme: string(None),
EnvZID: root.Environment().ZitiIdentity,
ShareMode: string(request.ShareMode),
FrontendSelection: request.Frontends,
BackendMode: string(request.BackendMode),
BackendProxyEndpoint: request.Target,
AuthScheme: string(None),
OauthEmailDomains: request.OauthEmailDomains,
OauthProvider: request.OauthProvider,
OauthAuthorizationCheckInterval: request.OauthAuthorizationCheckInterval.String(),
}
return req
}

View File

@ -577,6 +577,8 @@ paths:
description: unauthorized
404:
description: not found
422:
description: unprocessable
500:
description: internal server error
schema:
@ -978,6 +980,15 @@ definitions:
type: array
items:
$ref: "#/definitions/authUser"
oauthProvider:
type: string
enum: ["github", "google"]
oauthEmailDomains:
type: array
items:
type: string
oauthAuthorizationCheckInterval:
type: string
reserved:
type: boolean

View File

@ -253,6 +253,9 @@
* @property {string} backendProxyEndpoint
* @property {string} authScheme
* @property {module:types.authUser[]} authUsers
* @property {string} oauthProvider
* @property {string[]} oauthEmailDomains
* @property {string} oauthAuthorizationCheckInterval
* @property {boolean} reserved
*/