summaryrefslogtreecommitdiff
path: root/pkg/oidc
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/oidc')
-rw-r--r--pkg/oidc/id_token.go39
-rw-r--r--pkg/oidc/metadata.go22
-rw-r--r--pkg/oidc/oidc.go31
-rw-r--r--pkg/oidc/oidc_test.go49
4 files changed, 141 insertions, 0 deletions
diff --git a/pkg/oidc/id_token.go b/pkg/oidc/id_token.go
new file mode 100644
index 0000000..5fc1e63
--- /dev/null
+++ b/pkg/oidc/id_token.go
@@ -0,0 +1,39 @@
+package oidc
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "strings"
+ "time"
+
+ "github.com/xlgmokha/x/pkg/serde"
+)
+
+type IDToken struct {
+ Audience string `json:"aud"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified"`
+ ExpiredAt int64 `json:"exp"`
+ IssuedAt int64 `json:"iat"`
+ Issuer string `json:"iss"`
+ Name string `json:"name"`
+ Nickname string `json:"nickname"`
+ Picture string `json:"picture"`
+ Subject string `json:"sub"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+func NewIDToken(raw string) (*IDToken, error) {
+ sections := strings.SplitN(raw, ".", 3)
+ if len(sections) != 3 {
+ return nil, errors.New("Invalid token")
+ }
+ b, err := base64.RawURLEncoding.DecodeString(sections[1])
+ if err != nil {
+ return nil, err
+ }
+
+ token, err := serde.FromJSON[*IDToken](bytes.NewReader(b))
+ return token, err
+}
diff --git a/pkg/oidc/metadata.go b/pkg/oidc/metadata.go
new file mode 100644
index 0000000..8678f3b
--- /dev/null
+++ b/pkg/oidc/metadata.go
@@ -0,0 +1,22 @@
+package oidc
+
+type Metadata struct {
+ Issuer string `json:"issuer"`
+ AuthorizationEndpoint string `json:"authorization_endpoint"`
+ TokenEndpoint string `json:"token_endpoint"`
+ DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
+ UserInfoEndpoint string `json:"userinfo_endpoint"`
+ MFAChallengeEndpoint string `json:"mfa_challenge_endpoint"`
+ JWKSURI string `json:"jwks_uri"`
+ RegistrationEndpoint string `json:"registration_endpoint"`
+ RevocationEndpoint string `json:"revocation_endpoint"`
+ ScopesSupported []string `json:"scopes_supported"`
+ ResponseTypeSupported []string `json:"response_types_supported"`
+ CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
+ ResponseModesSupported []string `json:"response_modes_supported"`
+ SubjectTypesSupported []string `json:"subject_types_supported"`
+ IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
+ TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
+ ClaimsSupported []string `json:"claims_supported"`
+ RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
+}
diff --git a/pkg/oidc/oidc.go b/pkg/oidc/oidc.go
new file mode 100644
index 0000000..0526142
--- /dev/null
+++ b/pkg/oidc/oidc.go
@@ -0,0 +1,31 @@
+package oidc
+
+import (
+ "context"
+
+ "github.com/coreos/go-oidc/v3/oidc"
+ "golang.org/x/oauth2"
+)
+
+type OpenID struct {
+ Provider *oidc.Provider
+ Config *oauth2.Config
+}
+
+func New(ctx context.Context, issuer string, clientID, clientSecret, callbackURL string) (*OpenID, error) {
+ provider, err := oidc.NewProvider(ctx, issuer)
+ if err != nil {
+ return nil, err
+ }
+
+ return &OpenID{
+ Provider: provider,
+ Config: &oauth2.Config{
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ RedirectURL: callbackURL,
+ Endpoint: provider.Endpoint(),
+ Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
+ },
+ }, nil
+}
diff --git a/pkg/oidc/oidc_test.go b/pkg/oidc/oidc_test.go
new file mode 100644
index 0000000..ef6a4f6
--- /dev/null
+++ b/pkg/oidc/oidc_test.go
@@ -0,0 +1,49 @@
+package oidc
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/xlgmokha/x/pkg/serde"
+)
+
+func TestOpenID(t *testing.T) {
+ t.Run("GET /.well-known/openid-configuration", func(t *testing.T) {
+ srv := httptest.NewServer(nil)
+ srv.Config = &http.Server{
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "/.well-known/openid-configuration", r.URL.Path)
+ require.NoError(t, serde.ToJSON(w, &Metadata{
+ AuthorizationEndpoint: srv.URL + "/authorize",
+ ClaimsSupported: []string{"aud"},
+ CodeChallengeMethodsSupported: []string{"plain"},
+ DeviceAuthorizationEndpoint: srv.URL + "/device/authorize",
+ IDTokenSigningAlgValuesSupported: []string{"RS256"},
+ Issuer: srv.URL,
+ JWKSURI: srv.URL + "/jwks",
+ MFAChallengeEndpoint: srv.URL + "/mfa",
+ RegistrationEndpoint: srv.URL + "/users/new",
+ RequestURIParameterSupported: false,
+ ResponseModesSupported: []string{"query"},
+ ResponseTypeSupported: []string{"code"},
+ RevocationEndpoint: srv.URL + "/revoke",
+ ScopesSupported: []string{"oidc"},
+ SubjectTypesSupported: []string{"public"},
+ TokenEndpoint: srv.URL + "/token",
+ TokenEndpointAuthMethodsSupported: []string{"client_secret_post"},
+ UserInfoEndpoint: srv.URL + "/users/me",
+ }))
+ }),
+ }
+ defer srv.Close()
+
+ openID, err := New(context.Background(), srv.URL, "client_id", "client_secret", "https://example.com/oauth/callback")
+ require.NoError(t, err)
+
+ assert.Equal(t, srv.URL+"/authorize", openID.Provider.Endpoint().AuthURL)
+ })
+}