chore: oAuth Client get Authentification URL
This commit is contained in:
commit
f8f90c896c
10 changed files with 281 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
vendor
|
4
Readme.md
Normal file
4
Readme.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Small oAuth2 Client to have an easy way to connect to Authentik
|
||||||
|
|
||||||
|
# Links
|
||||||
|
* https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/
|
84
auth.go
Normal file
84
auth.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package kekskurseauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
config AuthConfig
|
||||||
|
clientID string
|
||||||
|
clientSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthWithConfig(config AuthConfig, clientID, clientSecret string) (Auth, error) {
|
||||||
|
a := Auth{}
|
||||||
|
a.config = config
|
||||||
|
a.clientID = clientID
|
||||||
|
a.clientSecret = clientSecret
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthWithConfigurationURL(url, clientID, clientSecret string) (Auth, error) {
|
||||||
|
a := Auth{}
|
||||||
|
a.clientID = clientID
|
||||||
|
a.clientSecret = clientSecret
|
||||||
|
config := AuthConfig{}
|
||||||
|
|
||||||
|
res, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
bodyContent, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(bodyContent, &config)
|
||||||
|
if err != nil {
|
||||||
|
return Auth{}, fmt.Errorf("%w: %q", ErrCantGetConfiguratorData, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.config = config
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Auth) GetAuthorizationURL(redirectUrl string, scope []string, state string) (string, error) {
|
||||||
|
if a.config.AuthorizationEndpoint == "" {
|
||||||
|
return "", fmt.Errorf("%w: %s", ErrCantGetAuthorizationURL, "AuthorizationEndpoint in config is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.clientID == "" {
|
||||||
|
return "", fmt.Errorf("%w: %s", ErrCantGetAuthorizationURL, "clientid in config is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse(a.config.AuthorizationEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%w: %q", ErrCantGetAuthorizationURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := url.Query()
|
||||||
|
|
||||||
|
values.Set("client_id", a.clientID)
|
||||||
|
if redirectUrl != "" {
|
||||||
|
values.Set("redirect_uri", redirectUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scope) > 0 {
|
||||||
|
values.Set("scope", strings.Join(scope, "+"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != "" {
|
||||||
|
values.Set("state", state)
|
||||||
|
}
|
||||||
|
|
||||||
|
url.RawQuery = values.Encode()
|
||||||
|
|
||||||
|
return url.String(), nil
|
||||||
|
}
|
75
auth_test.go
Normal file
75
auth_test.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package kekskurseauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAuthWithConfig(t *testing.T) {
|
||||||
|
config := AuthConfig{}
|
||||||
|
config.TokenEndpoint = "http://localhost/something"
|
||||||
|
client, err := NewAuthWithConfig(config, "abc", "def")
|
||||||
|
assert.Equal(t, nil, err, "should return no error while creating Auth")
|
||||||
|
assert.Equal(t, "http://localhost/something", client.config.TokenEndpoint, "should have currect config")
|
||||||
|
assert.Equal(t, "abc", client.clientID, "should have stored currect clientid")
|
||||||
|
assert.Equal(t, "def", client.clientSecret, "should have stored currect client secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAuthWithConfigurationURL(t *testing.T) {
|
||||||
|
client, err := NewAuthWithConfigurationURL("http://localhost:8084/openid-configuration", "abc", "def")
|
||||||
|
assert.Nil(t, err, "should create client without any error")
|
||||||
|
assert.Equal(t, "https://auth.keks.cloud/application/o/token/", client.config.TokenEndpoint, "token endpoint should match")
|
||||||
|
assert.Equal(t, "abc", client.clientID, "should have stored currect clientid")
|
||||||
|
assert.Equal(t, "def", client.clientSecret, "should have stored currect client secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAuthorizationUrl(t *testing.T) {
|
||||||
|
tts := []struct {
|
||||||
|
name string
|
||||||
|
config AuthConfig
|
||||||
|
redirectURL string
|
||||||
|
scops []string
|
||||||
|
state string
|
||||||
|
exptUrl string
|
||||||
|
exptError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "error-config-has-no-url",
|
||||||
|
exptError: ErrCantGetAuthorizationURL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plain-url",
|
||||||
|
config: AuthConfig{AuthorizationEndpoint: "http://localhost/something"},
|
||||||
|
exptUrl: "http://localhost/something?client_id=abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url-with-redirect-and-state",
|
||||||
|
config: AuthConfig{AuthorizationEndpoint: "http://localhost/something"},
|
||||||
|
exptUrl: "http://localhost/something?client_id=abc&redirect_uri=https%3A%2F%2Fexample.com&state=randomStateStringWith%C3%A4and%C3%B6ok",
|
||||||
|
redirectURL: "https://example.com",
|
||||||
|
state: "randomStateStringWithäandöok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url-with-scopes",
|
||||||
|
config: AuthConfig{AuthorizationEndpoint: "http://localhost/something"},
|
||||||
|
scops: []string{"some", "söäüöäüßcopes"},
|
||||||
|
exptUrl: "http://localhost/something?client_id=abc&scope=some%2Bs%C3%B6%C3%A4%C3%BC%C3%B6%C3%A4%C3%BC%C3%9Fcopes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tts {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
client, err := NewAuthWithConfig(tt.config, "abc", "def")
|
||||||
|
assert.Nil(t, err, "should be able to create client without error")
|
||||||
|
|
||||||
|
url, err := client.GetAuthorizationURL(tt.redirectURL, tt.scops, tt.state)
|
||||||
|
if tt.exptError == nil {
|
||||||
|
assert.Nil(t, err, "should get link without error")
|
||||||
|
} else {
|
||||||
|
assert.ErrorIs(t, err, tt.exptError, "should return right error")
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.exptUrl, url, "should return right url")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
config.go
Normal file
7
config.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package kekskurseauth
|
||||||
|
|
||||||
|
type AuthConfig struct {
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
|
}
|
7
docker-compose.yml
Normal file
7
docker-compose.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
services:
|
||||||
|
oAuthDummyServer:
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- ./static/openid-configuration:/usr/share/nginx/html/openid-configuration
|
||||||
|
ports:
|
||||||
|
- 8084:80
|
8
errors.go
Normal file
8
errors.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package kekskurseauth
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCantGetConfiguratorData = errors.New("cant get data from configurator url")
|
||||||
|
ErrCantGetAuthorizationURL = errors.New("cant get url to recirect user to")
|
||||||
|
)
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module git.keks.cloud/kekskurse/kekskurse-auth
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.9.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
74
static/openid-configuration
Normal file
74
static/openid-configuration
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"issuer": "https://auth.keks.cloud/application/o/test/",
|
||||||
|
"authorization_endpoint": "https://auth.keks.cloud/application/o/authorize/",
|
||||||
|
"token_endpoint": "https://auth.keks.cloud/application/o/token/",
|
||||||
|
"userinfo_endpoint": "https://auth.keks.cloud/application/o/userinfo/",
|
||||||
|
"end_session_endpoint": "https://auth.keks.cloud/application/o/test/end-session/",
|
||||||
|
"introspection_endpoint": "https://auth.keks.cloud/application/o/introspect/",
|
||||||
|
"revocation_endpoint": "https://auth.keks.cloud/application/o/revoke/",
|
||||||
|
"device_authorization_endpoint": "https://auth.keks.cloud/application/o/device/",
|
||||||
|
"response_types_supported": [
|
||||||
|
"code",
|
||||||
|
"id_token",
|
||||||
|
"id_token token",
|
||||||
|
"code token",
|
||||||
|
"code id_token",
|
||||||
|
"code id_token token"
|
||||||
|
],
|
||||||
|
"response_modes_supported": [
|
||||||
|
"query",
|
||||||
|
"fragment",
|
||||||
|
"form_post"
|
||||||
|
],
|
||||||
|
"jwks_uri": "https://auth.keks.cloud/application/o/test/jwks/",
|
||||||
|
"grant_types_supported": [
|
||||||
|
"authorization_code",
|
||||||
|
"refresh_token",
|
||||||
|
"implicit",
|
||||||
|
"client_credentials",
|
||||||
|
"password",
|
||||||
|
"urn:ietf:params:oauth:grant-type:device_code"
|
||||||
|
],
|
||||||
|
"id_token_signing_alg_values_supported": [
|
||||||
|
"RS256"
|
||||||
|
],
|
||||||
|
"subject_types_supported": [
|
||||||
|
"public"
|
||||||
|
],
|
||||||
|
"token_endpoint_auth_methods_supported": [
|
||||||
|
"client_secret_post",
|
||||||
|
"client_secret_basic"
|
||||||
|
],
|
||||||
|
"acr_values_supported": [
|
||||||
|
"goauthentik.io/providers/oauth2/default"
|
||||||
|
],
|
||||||
|
"scopes_supported": [
|
||||||
|
"profile",
|
||||||
|
"openid",
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"request_parameter_supported": false,
|
||||||
|
"claims_supported": [
|
||||||
|
"sub",
|
||||||
|
"iss",
|
||||||
|
"aud",
|
||||||
|
"exp",
|
||||||
|
"iat",
|
||||||
|
"auth_time",
|
||||||
|
"acr",
|
||||||
|
"amr",
|
||||||
|
"nonce",
|
||||||
|
"email",
|
||||||
|
"email_verified",
|
||||||
|
"name",
|
||||||
|
"given_name",
|
||||||
|
"preferred_username",
|
||||||
|
"nickname",
|
||||||
|
"groups"
|
||||||
|
],
|
||||||
|
"claims_parameter_supported": false,
|
||||||
|
"code_challenge_methods_supported": [
|
||||||
|
"plain",
|
||||||
|
"S256"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue