mirror of
https://github.com/davidallendj/opaal.git
synced 2025-12-20 03:27:02 -07:00
Refactored and reorganized code
This commit is contained in:
parent
86f8784c19
commit
fdb0db389c
8 changed files with 384 additions and 127 deletions
|
|
@ -1,7 +1,9 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"davidallendj/oidc-auth/internal/util"
|
"davidallendj/opal/internal/oauth"
|
||||||
|
"davidallendj/opal/internal/oidc"
|
||||||
|
"davidallendj/opal/internal/util"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -11,35 +13,51 @@ import (
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Server struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
RedirectUri []string `yaml:"redirect-uri"`
|
}
|
||||||
|
|
||||||
|
type AuthEndpoints struct {
|
||||||
|
Identities string `yaml:"identities"`
|
||||||
|
TrustedIssuers string `yaml:"trusted-issuers"`
|
||||||
|
AccessToken string `yaml:"access-token"`
|
||||||
|
ServerConfig string `yaml:"server-config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server Server `yaml:"server"`
|
||||||
|
Client oauth.Client `yaml:"client"`
|
||||||
|
IdentityProvider oidc.IdentityProvider `yaml:"oidc"`
|
||||||
State string `yaml:"state"`
|
State string `yaml:"state"`
|
||||||
ResponseType string `yaml:"response-type"`
|
ResponseType string `yaml:"response-type"`
|
||||||
Scope []string `yaml:"scope"`
|
Scope []string `yaml:"scope"`
|
||||||
ClientId string `yaml:"client.id"`
|
AuthEndpoints AuthEndpoints `yaml:"urls"`
|
||||||
ClientSecret string `yaml:"client.secret"`
|
OpenBrowser bool `yaml:"open-browser"`
|
||||||
OIDCHost string `yaml:"oidc.host"`
|
|
||||||
OIDCPort int `yaml:"oidc.port"`
|
|
||||||
IdentitiesUrl string `yaml:"identities-url"`
|
|
||||||
AccessTokenUrl string `yaml:"access-token-url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() Config {
|
func NewConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
|
Server: Server{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 3333,
|
Port: 3333,
|
||||||
RedirectUri: []string{""},
|
},
|
||||||
|
Client: oauth.Client{
|
||||||
|
Id: "",
|
||||||
|
Secret: "",
|
||||||
|
RedirectUris: []string{""},
|
||||||
|
},
|
||||||
|
IdentityProvider: *oidc.NewIdentityProvider(),
|
||||||
State: util.RandomString(20),
|
State: util.RandomString(20),
|
||||||
ResponseType: "code",
|
ResponseType: "code",
|
||||||
Scope: []string{"openid", "profile", "email"},
|
Scope: []string{"openid", "profile", "email"},
|
||||||
ClientId: "",
|
AuthEndpoints: AuthEndpoints{
|
||||||
ClientSecret: "",
|
Identities: "",
|
||||||
OIDCHost: "127.0.0.1",
|
AccessToken: "",
|
||||||
OIDCPort: 80,
|
TrustedIssuers: "",
|
||||||
IdentitiesUrl: "",
|
ServerConfig: "",
|
||||||
AccessTokenUrl: "",
|
},
|
||||||
|
OpenBrowser: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
110
cmd/login.go
110
cmd/login.go
|
|
@ -1,9 +1,9 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"davidallendj/oidc-auth/internal/api"
|
"davidallendj/opal/internal/api"
|
||||||
"davidallendj/oidc-auth/internal/oidc"
|
"davidallendj/opal/internal/oidc"
|
||||||
"davidallendj/oidc-auth/internal/util"
|
"davidallendj/opal/internal/util"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -13,83 +13,123 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func hasRequiredParams(config *Config) bool {
|
||||||
identitiesUrl = ""
|
return config.Client.Id != "" && config.Client.Secret != ""
|
||||||
accessTokenUrl = ""
|
}
|
||||||
)
|
|
||||||
|
|
||||||
var loginCmd = &cobra.Command{
|
var loginCmd = &cobra.Command{
|
||||||
Use: "login",
|
Use: "login",
|
||||||
Short: "Start the login flow",
|
Short: "Start the login flow",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// load config if found
|
||||||
if configPath != "" {
|
if configPath != "" {
|
||||||
|
exists, err := util.PathExists(configPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to load config")
|
||||||
|
os.Exit(1)
|
||||||
|
} else if exists {
|
||||||
config = LoadConfig(configPath)
|
config = LoadConfig(configPath)
|
||||||
} else {
|
} else {
|
||||||
config = NewConfig()
|
config = NewConfig()
|
||||||
}
|
}
|
||||||
oidcProvider := oidc.NewOIDCProvider()
|
}
|
||||||
oidcProvider.Host = config.OIDCHost
|
// try and fetch server configuration if provided URL
|
||||||
oidcProvider.Port = config.OIDCPort
|
idp := oidc.NewIdentityProvider()
|
||||||
|
if config.AuthEndpoints.ServerConfig != "" {
|
||||||
|
idp.FetchServerConfig(config.AuthEndpoints.ServerConfig)
|
||||||
|
} else {
|
||||||
|
// otherwise, use what's provided in config file
|
||||||
|
idp.Issuer = config.IdentityProvider.Issuer
|
||||||
|
idp.Endpoints = config.IdentityProvider.Endpoints
|
||||||
|
idp.Supported = config.IdentityProvider.Supported
|
||||||
|
}
|
||||||
|
|
||||||
// check if the client ID is set
|
// check if all appropriate parameters are set in config
|
||||||
if config.ClientId == "" {
|
if !hasRequiredParams(&config) {
|
||||||
fmt.Printf("client ID must be set\n")
|
fmt.Printf("client ID must be set\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build the authorization URL to redirect user for social sign-in
|
||||||
var authorizationUrl = util.BuildAuthorizationUrl(
|
var authorizationUrl = util.BuildAuthorizationUrl(
|
||||||
oidcProvider.GetAuthorizeUrl(),
|
idp.Endpoints.Authorize,
|
||||||
config.ClientId,
|
config.Client.Id,
|
||||||
config.RedirectUri,
|
config.Client.RedirectUris,
|
||||||
config.State,
|
config.State,
|
||||||
config.ResponseType,
|
config.ResponseType,
|
||||||
config.Scope,
|
config.Scope,
|
||||||
)
|
)
|
||||||
|
|
||||||
// print the authorization URL for the user to log in
|
// print the authorization URL for sharing
|
||||||
fmt.Printf("login with identity provider: %s\n", authorizationUrl)
|
serverAddr := fmt.Sprintf("%s:%d", config.IdentityProvider.Issuer)
|
||||||
|
fmt.Printf(`Login with identity provider:
|
||||||
|
%s/login
|
||||||
|
%s\n`,
|
||||||
|
serverAddr, authorizationUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
// automatically open browser to initiate login flow (only useful for testing)
|
||||||
|
if config.OpenBrowser {
|
||||||
|
util.OpenUrl(authorizationUrl)
|
||||||
|
}
|
||||||
|
|
||||||
// authorize oauth client and listen for callback from provider
|
// authorize oauth client and listen for callback from provider
|
||||||
fmt.Printf("waiting for response from OIDC provider...\n")
|
fmt.Printf("Waiting for authorization code redirect @%s/oidc/callback...\n", serverAddr)
|
||||||
code, err := api.WaitForAuthorizationCode(config.Host, config.Port)
|
code, err := api.WaitForAuthorizationCode(serverAddr, authorizationUrl)
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
fmt.Printf("server closed\n")
|
fmt.Printf("Server closed.\n")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fmt.Printf("error starting server: %s\n", err)
|
fmt.Printf("Error starting server: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use code from response and exchange for bearer token
|
// use code from response and exchange for bearer token (with ID token)
|
||||||
tokenString, err := api.FetchToken(code, oidcProvider.GetTokenUrl(), config.ClientId, config.ClientSecret, config.State, config.RedirectUri)
|
tokenString, err := api.FetchIssuerToken(
|
||||||
|
code,
|
||||||
|
idp.Endpoints.Token,
|
||||||
|
config.Client,
|
||||||
|
config.State,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Printf("%v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extract ID token from bearer as JSON string for easy consumption
|
||||||
var data map[string]any
|
var data map[string]any
|
||||||
json.Unmarshal([]byte(tokenString), &data)
|
json.Unmarshal([]byte(tokenString), &data)
|
||||||
idToken := data["id_token"].(string)
|
idToken := data["id_token"].(string)
|
||||||
|
|
||||||
// create a new identity with Ory Kratos if identitiesUrl is provided
|
// create a new identity with identity and session manager if url is provided
|
||||||
if config.IdentitiesUrl != "" {
|
if config.AuthEndpoints.Identities != "" {
|
||||||
api.CreateIdentity(config.IdentitiesUrl, idToken)
|
api.CreateIdentity(config.AuthEndpoints.Identities, idToken)
|
||||||
api.FetchIdentities(config.IdentitiesUrl)
|
api.FetchIdentities(config.AuthEndpoints.Identities)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use ID token/user info to get access token from Ory Hydra
|
// fetch JWKS and add issuer to authentication server to submit ID token
|
||||||
if config.AccessTokenUrl != "" {
|
jwk, err := api.FetchJwk("")
|
||||||
api.FetchAccessToken(config.AccessTokenUrl, config.ClientId, idToken)
|
if err != nil {
|
||||||
|
fmt.Printf("failed to fetch JWK: %v\n", err)
|
||||||
|
} else {
|
||||||
|
api.AddTrustedIssuer(config.AuthEndpoints.TrustedIssuers, jwk.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// use ID token/user info to fetch access token from authentication server
|
||||||
|
if config.AuthEndpoints.AccessToken != "" {
|
||||||
|
api.FetchAccessToken(config.AuthEndpoints.AccessToken, config.Client.Id, idToken, config.Scope)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
loginCmd.Flags().StringVar(&config.ClientId, "client.id", config.ClientId, "set the client ID")
|
loginCmd.Flags().StringVar(&config.Client.Id, "client.id", config.Client.Id, "set the client ID")
|
||||||
loginCmd.Flags().StringVar(&config.ClientSecret, "client.secret", config.ClientSecret, "set the client secret")
|
loginCmd.Flags().StringVar(&config.Client.Secret, "client.secret", config.Client.Secret, "set the client secret")
|
||||||
loginCmd.Flags().StringSliceVar(&config.RedirectUri, "redirect-uri", config.RedirectUri, "set the redirect URI")
|
loginCmd.Flags().StringSliceVar(&config.Client.RedirectUris, "redirect-uri", config.Client.RedirectUris, "set the redirect URI")
|
||||||
loginCmd.Flags().StringVar(&config.ResponseType, "response-type", config.ResponseType, "set the response-type")
|
loginCmd.Flags().StringVar(&config.ResponseType, "response-type", config.ResponseType, "set the response-type")
|
||||||
loginCmd.Flags().StringSliceVar(&config.Scope, "scope", config.Scope, "set the scopes")
|
loginCmd.Flags().StringSliceVar(&config.Scope, "scope", config.Scope, "set the scopes")
|
||||||
loginCmd.Flags().StringVar(&config.State, "state", config.State, "set the state")
|
loginCmd.Flags().StringVar(&config.State, "state", config.State, "set the state")
|
||||||
loginCmd.Flags().StringVar(&config.Host, "host", config.Host, "set the listening host")
|
loginCmd.Flags().StringVar(&config.Server.Host, "host", config.Server.Host, "set the listening host")
|
||||||
loginCmd.Flags().IntVar(&config.Port, "port", config.Port, "set the listening port")
|
loginCmd.Flags().IntVar(&config.Server.Port, "port", config.Server.Port, "set the listening port")
|
||||||
|
loginCmd.Flags().BoolVar(&config.OpenBrowser, "open-browser", config.OpenBrowser, "automatically open link in browser")
|
||||||
rootCmd.AddCommand(loginCmd)
|
rootCmd.AddCommand(loginCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
go.mod
12
go.mod
|
|
@ -1,13 +1,23 @@
|
||||||
module davidallendj/oidc-auth
|
module davidallendj/opal
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/lestrrat-go/jwx v1.2.28
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
|
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||||
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
74
go.sum
74
go.sum
|
|
@ -1,13 +1,87 @@
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||||
|
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
|
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||||
|
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||||
|
github.com/lestrrat-go/jwx v1.2.28 h1:uadI6o0WpOVrBSf498tRXZIwPpEtLnR9CvqPFXeI5sA=
|
||||||
|
github.com/lestrrat-go/jwx v1.2.28/go.mod h1:nF+91HEMh/MYFVwKPl5HHsBGMPscqbQb+8IDQdIazP8=
|
||||||
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||||
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,65 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"davidallendj/opal/internal/oauth"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WaitForAuthorizationCode(host string, port int) (string, error) {
|
func WaitForAuthorizationCode(serverAddr string, loginUrl string) (string, error) {
|
||||||
var code string
|
var code string
|
||||||
s := &http.Server{
|
s := &http.Server{Addr: serverAddr}
|
||||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
// redirect directly to identity provider with this endpoint
|
||||||
|
http.Redirect(w, r, loginUrl, http.StatusSeeOther)
|
||||||
|
})
|
||||||
http.HandleFunc("/oidc/callback", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/oidc/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// get the code from the OIDC provider
|
// get the code from the OIDC provider
|
||||||
if r != nil {
|
if r != nil {
|
||||||
code = r.URL.Query().Get("code")
|
code = r.URL.Query().Get("code")
|
||||||
fmt.Printf("authorization code: %v\n", code)
|
fmt.Printf("Authorization code: %v\n", code)
|
||||||
}
|
}
|
||||||
s.Close()
|
s.Close()
|
||||||
|
|
||||||
})
|
})
|
||||||
fmt.Printf("listening for authorization code at %s/oidc/callback\n", s.Addr)
|
|
||||||
return code, s.ListenAndServe()
|
return code, s.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchToken(code string, remoteUrl string, clientId string, clientSecret string, state string, redirectUri []string) (string, error) {
|
func FetchIssuerToken(code string, remoteUrl string, client oauth.Client, state string) (string, error) {
|
||||||
var token string
|
var token string
|
||||||
data := url.Values{
|
data := url.Values{
|
||||||
"grant_type": {"authorization_code"},
|
"grant_type": {"authorization_code"},
|
||||||
"code": {code},
|
"code": {code},
|
||||||
"client_id": {clientId},
|
"client_id": {client.Id},
|
||||||
"client_secret": {clientSecret},
|
"client_secret": {client.Secret},
|
||||||
"state": {state},
|
"state": {state},
|
||||||
"redirect_uri": {strings.Join(redirectUri, ",")},
|
"redirect_uri": {strings.Join(client.RedirectUris, ",")},
|
||||||
|
}
|
||||||
|
res, err := http.PostForm(remoteUrl, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get ID token: %s", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read response body: %v", err)
|
||||||
|
}
|
||||||
|
token = string(b)
|
||||||
|
|
||||||
|
fmt.Printf("%v\n", token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchAccessToken(remoteUrl string, clientId string, jwt string, scopes []string) (string, error) {
|
||||||
|
// hydra endpoint: /oauth/token
|
||||||
|
var token string
|
||||||
|
data := url.Values{
|
||||||
|
"grant_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
|
||||||
|
"assertion": {jwt},
|
||||||
}
|
}
|
||||||
res, err := http.PostForm(remoteUrl, data)
|
res, err := http.PostForm(remoteUrl, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -53,31 +78,34 @@ func FetchToken(code string, remoteUrl string, clientId string, clientSecret str
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchAccessToken(remoteUrl string, clientId string, jwt string) (string, error) {
|
func AddTrustedIssuer(remoteUrl string, issuer string, subject string, duration time.Duration, jwk string, scope []string) error {
|
||||||
var token string
|
// hydra endpoint: /admin/trust/grants/jwt-bearer/issuers
|
||||||
data := url.Values{
|
data := []byte(fmt.Sprintf(`{
|
||||||
"grant_type": {"client_credentials"},
|
"allow_any_subject": false,
|
||||||
"client_id": {clientId},
|
"issuer": "%s",
|
||||||
"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
|
"subject": "%s"
|
||||||
"client_assertion": {jwt},
|
"expires_at": "%v"
|
||||||
}
|
"jwk": %v,
|
||||||
res, err := http.PostForm(remoteUrl, data)
|
"scope": [ j%s ],
|
||||||
if err != nil {
|
}`, issuer, subject, time.Now().Add(duration), jwk, strings.Join(scope, ",")))
|
||||||
return "", fmt.Errorf("failed to get token: %s", err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
b, err := io.ReadAll(res.Body)
|
req, err := http.NewRequest("POST", remoteUrl, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to read response body: %v", err)
|
return fmt.Errorf("failed to create a new request: %v", err)
|
||||||
}
|
}
|
||||||
token = string(b)
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
// req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
|
||||||
fmt.Printf("%v\n", token)
|
client := &http.Client{}
|
||||||
return token, nil
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to do request: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%d\n", res.StatusCode)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateIdentity(remoteUrl string, idToken string) error {
|
func CreateIdentity(remoteUrl string, idToken string) error {
|
||||||
|
// kratos endpoint: /admin/identities
|
||||||
data := []byte(`{
|
data := []byte(`{
|
||||||
"schema_id": "preset://email",
|
"schema_id": "preset://email",
|
||||||
"traits": {
|
"traits": {
|
||||||
|
|
@ -114,3 +142,7 @@ func FetchIdentities(remoteUrl string) error {
|
||||||
fmt.Printf("%v\n", res)
|
fmt.Printf("%v\n", res)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RedirectSuccess() {
|
||||||
|
// show a success page with the user's access token
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
package oauth
|
package oauth
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Id string
|
Id string `yaml:"id"`
|
||||||
Secret string
|
Secret string `yaml:"secret"`
|
||||||
Issuer string
|
RedirectUris []string `yaml:"redirect_uris"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient() *Client {
|
func NewClient() *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
Id: "",
|
Id: "",
|
||||||
Secret: "",
|
Secret: "",
|
||||||
Issuer: "",
|
RedirectUris: []string{""},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,100 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
type OpenIDConnectProvider struct {
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
Host string
|
)
|
||||||
Port int
|
|
||||||
AuthorizeEndpoint string
|
type IdentityProvider struct {
|
||||||
TokenEndpoint string
|
Issuer string `json:"issuer" yaml:"issuer"`
|
||||||
ConfigEndpoint string
|
Endpoints Endpoints `json:"endpoints" yaml:"endpoints"`
|
||||||
|
Supported Supported `json:"supported" yaml:"supported"`
|
||||||
|
Key jwk.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOIDCProvider() *OpenIDConnectProvider {
|
type Endpoints struct {
|
||||||
return &OpenIDConnectProvider{
|
Authorize string `json:"authorize_endpoint" yaml:"authorize"`
|
||||||
Host: "127.0.0.1",
|
Token string `json:"token_endpoint" yaml:"token"`
|
||||||
Port: 80,
|
Revocation string `json:"revocation_endpoint" yaml:"revocation"`
|
||||||
AuthorizeEndpoint: "/oauth/authorize",
|
Introspection string `json:"introspection_endpoint" yaml:"introspection"`
|
||||||
TokenEndpoint: "/oauth/token",
|
UserInfo string `json:"userinfo_endpoint" yaml:"userinfo"`
|
||||||
|
Jwks string `json:"jwks_uri" yaml:"jwks_uri"`
|
||||||
|
}
|
||||||
|
type Supported struct {
|
||||||
|
ResponseTypes []string `json:"response_types_supported"`
|
||||||
|
ResponseModes []string `json:"response_modes_supported"`
|
||||||
|
GrantTypes []string `json:"grant_types_supported"`
|
||||||
|
TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported"`
|
||||||
|
SubjectTypes []string `json:"subject_types_supported"`
|
||||||
|
IdTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
ClaimTypes []string `json:"claim_types_supported"`
|
||||||
|
Claims []string `json:"claims_supported"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIdentityProvider() *IdentityProvider {
|
||||||
|
p := &IdentityProvider{Issuer: "127.0.0.1"}
|
||||||
|
p.Endpoints = Endpoints{
|
||||||
|
Authorize: p.Issuer + "/oauth/authorize",
|
||||||
|
Token: p.Issuer + "/oauth/token",
|
||||||
|
Revocation: p.Issuer + "/oauth/revocation",
|
||||||
|
Introspection: p.Issuer + "/oauth/introspect",
|
||||||
|
UserInfo: p.Issuer + "/oauth/userinfo",
|
||||||
|
Jwks: p.Issuer + "/oauth/discovery/keys",
|
||||||
}
|
}
|
||||||
}
|
p.Supported = Supported{
|
||||||
|
ResponseTypes: []string{"code"},
|
||||||
func (oidc *OpenIDConnectProvider) GetAuthorizeUrl() string {
|
ResponseModes: []string{"query"},
|
||||||
if oidc.Port != 80 {
|
GrantTypes: []string{
|
||||||
return fmt.Sprintf("%s:%d", oidc.Host, oidc.Port) + oidc.AuthorizeEndpoint
|
"authorization_code",
|
||||||
|
"client_credentials",
|
||||||
|
"refresh_token",
|
||||||
|
},
|
||||||
|
TokenEndpointAuthMethods: []string{
|
||||||
|
"client_secret_basic",
|
||||||
|
"client_secret_post",
|
||||||
|
},
|
||||||
|
SubjectTypes: []string{"public"},
|
||||||
|
IdTokenSigningAlgValues: []string{"RS256"},
|
||||||
|
ClaimTypes: []string{"normal"},
|
||||||
|
Claims: []string{
|
||||||
|
"iss",
|
||||||
|
"sub",
|
||||||
|
"aud",
|
||||||
|
"exp",
|
||||||
|
"iat",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return oidc.Host + oidc.AuthorizeEndpoint
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oidc *OpenIDConnectProvider) GetTokenUrl() string {
|
func (p *IdentityProvider) FetchServerConfig(url string) {
|
||||||
if oidc.Port != 80 {
|
|
||||||
return fmt.Sprintf("%s:%d", oidc.Host, oidc.Port) + oidc.TokenEndpoint
|
|
||||||
}
|
|
||||||
return oidc.Host + oidc.TokenEndpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oidc *OpenIDConnectProvider) FetchServerConfiguration(url string) {
|
|
||||||
// make a request to a server's openid-configuration
|
// make a request to a server's openid-configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *IdentityProvider) FetchJwk(url string) error {
|
||||||
|
//
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
set, err := jwk.Fetch(ctx, url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
// get the first JWK from set
|
||||||
|
for it := set.Iterate(context.Background()); it.Next(context.Background()); {
|
||||||
|
pair := it.Pair()
|
||||||
|
p.Key = pair.Value.(jwk.Key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to load public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IdentityProvider) GetRawJwk() (any, error) {
|
||||||
|
var rawkey any
|
||||||
|
if err := p.Key.Raw(&rawkey); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get raw key: %v", err)
|
||||||
|
}
|
||||||
|
return rawkey, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -58,3 +60,22 @@ func PathExists(path string) (bool, error) {
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/39320371/how-start-web-server-to-open-page-in-browser-in-golang
|
||||||
|
// open opens the specified URL in the default browser of the user.
|
||||||
|
func OpenUrl(url string) error {
|
||||||
|
var cmd string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
cmd = "cmd"
|
||||||
|
args = []string{"/c", "start"}
|
||||||
|
case "darwin":
|
||||||
|
cmd = "open"
|
||||||
|
default: // "linux", "freebsd", "openbsd", "netbsd"
|
||||||
|
cmd = "xdg-open"
|
||||||
|
}
|
||||||
|
args = append(args, url)
|
||||||
|
return exec.Command(cmd, args...).Start()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue