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