diff options
| author | mo khan <mo@mokhan.ca> | 2025-04-14 15:53:32 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-04-14 15:53:32 -0600 |
| commit | b12eb55fdb603290e3bc62880f6e9dff538571de (patch) | |
| tree | a9cfde922e251391f0618f9837d7b63a94156664 /pkg | |
| parent | bb577738ac0359f8c8da0902b5c18af789ddf29d (diff) | |
feat: connect the sessions controller to oidc provider
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/oidc/id_token.go | 39 | ||||
| -rw-r--r-- | pkg/oidc/metadata.go | 22 | ||||
| -rw-r--r-- | pkg/oidc/oidc.go | 31 | ||||
| -rw-r--r-- | pkg/oidc/oidc_test.go | 49 |
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) + }) +} |
