chore: oAuth Client get Authentification URL

This commit is contained in:
kekskurse 2024-09-12 19:39:24 +02:00
commit f8f90c896c
10 changed files with 281 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
vendor

4
Readme.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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=

View 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"
]
}