first draft of a new server interface
This commit is contained in:
parent
daf82a5e04
commit
d6a9c0bbb9
9 changed files with 481 additions and 9 deletions
269
pkg/op/server.go
Normal file
269
pkg/op/server.go
Normal file
|
@ -0,0 +1,269 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
jose "github.com/go-jose/go-jose/v3"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
)
|
||||
|
||||
type StatusError struct {
|
||||
parent error
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func NewStatusError(parent error, statusCode int) StatusError {
|
||||
return StatusError{
|
||||
parent: parent,
|
||||
statusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (e StatusError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", http.StatusText(e.statusCode), e.parent.Error())
|
||||
}
|
||||
|
||||
func (e StatusError) Unwrap() error {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
func (e StatusError) Is(err error) bool {
|
||||
var target StatusError
|
||||
if !errors.As(err, &target) {
|
||||
return false
|
||||
}
|
||||
return errors.Is(e.parent, target.parent) &&
|
||||
e.statusCode == target.statusCode
|
||||
}
|
||||
|
||||
type Request[T any] struct {
|
||||
Method string
|
||||
URL *url.URL
|
||||
Header http.Header
|
||||
Form url.Values
|
||||
Data *T
|
||||
}
|
||||
|
||||
func newRequest[T any](r *http.Request, data *T) *Request[T] {
|
||||
return &Request[T]{
|
||||
Method: r.Method,
|
||||
URL: r.URL,
|
||||
Header: r.Header,
|
||||
Form: r.Form,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type ClientRequest[T any] struct {
|
||||
*Request[T]
|
||||
Client Client
|
||||
}
|
||||
|
||||
func newClientRequest[T any](r *http.Request, data *T, client Client) *ClientRequest[T] {
|
||||
return &ClientRequest[T]{
|
||||
Request: newRequest[T](r, data),
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type Response[T any] struct {
|
||||
Header http.Header
|
||||
Data *T
|
||||
}
|
||||
|
||||
func NewResponse[T any](data *T) *Response[T] {
|
||||
return &Response[T]{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (resp *Response[T]) writeOut(w http.ResponseWriter) {
|
||||
gu.MapMerge(resp.Header, w.Header())
|
||||
json.NewEncoder(w).Encode(resp.Data)
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
// Health should return a status of "ok" once the Server is listining.
|
||||
Health(context.Context, *Request[struct{}]) (*Response[Status], error)
|
||||
|
||||
// Ready should return a status of "ok" once all dependecies,
|
||||
// such as database storage are ready.
|
||||
// An error can be returned to explain what is not ready.
|
||||
Ready(context.Context, *Request[struct{}]) (*Response[Status], error)
|
||||
|
||||
// Discovery return the OpenID Provider Configuration Information for this server.
|
||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
|
||||
Discovery(context.Context, *Request[struct{}]) (*Response[oidc.DiscoveryConfiguration], error)
|
||||
|
||||
// Authorize initiates the authorization flow and redirects to a login page.
|
||||
// See the various https://openid.net/specs/openid-connect-core-1_0.html
|
||||
// authorize endpoint sections (one for each type of flow).
|
||||
Authorize(context.Context, *Request[oidc.AuthRequest]) (*Response[url.URL], error)
|
||||
|
||||
// AuthorizeCallback? Do we still need it?
|
||||
|
||||
// DeviceAuthorization initiates the device authorization flow.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
|
||||
DeviceAuthorization(context.Context, *Request[oidc.DeviceAuthorizationRequest]) (*Response[oidc.DeviceAuthorizationResponse], error)
|
||||
|
||||
// VerifyClient is called on most oauth/token handlers to authenticate,
|
||||
// using either a secret (POST, Basic) or assertion (JWT).
|
||||
// If no secrets are provided, the client must be public.
|
||||
// This method is called before each method that takes a
|
||||
// [ClientRequest] argument.
|
||||
VerifyClient(context.Context, *Request[ClientCredentials]) (Client, error)
|
||||
|
||||
// CodeExchange returns Tokens after an authorization code
|
||||
// is obtained in a succesfull Authorize flow.
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value authorization_code
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
||||
CodeExchange(context.Context, *ClientRequest[oidc.AccessTokenRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// RefreshToken returns new Tokens after verifying a Refresh token.
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value refresh_token
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
||||
RefreshToken(context.Context, *ClientRequest[oidc.RefreshTokenRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value urn:ietf:params:oauth:grant-type:jwt-bearer
|
||||
// https://datatracker.ietf.org/doc/html/rfc7523#section-2.1
|
||||
JWTProfile(context.Context, *Request[oidc.JWTProfileGrantRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// TokenExchange handles the OAuth 2.0 token exchange grant
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value urn:ietf:params:oauth:grant-type:token-exchange
|
||||
// https://datatracker.ietf.org/doc/html/rfc8693
|
||||
TokenExchange(context.Context, *ClientRequest[oidc.TokenExchangeRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// ClientCredentialsExchange handles the OAuth 2.0 client credentials grant
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value client_credentials
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
|
||||
ClientCredentialsExchange(context.Context, *ClientRequest[oidc.ClientCredentialsRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// DeviceToken handles the OAuth 2.0 Device Authorization Grant
|
||||
// It is called by the Token endpoint handler when
|
||||
// grant_type has the value urn:ietf:params:oauth:grant-type:device_code.
|
||||
// It is typically called in a polling fashion and appropiate errors
|
||||
// should be returned to signal authorization_pending or access_denied etc.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.4,
|
||||
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5.
|
||||
DeviceToken(context.Context, *ClientRequest[oidc.DeviceAccessTokenRequest]) (*Response[oidc.AccessTokenResponse], error)
|
||||
|
||||
// Introspect handles the OAuth 2.0 Token Introspection endpoint.
|
||||
// https://datatracker.ietf.org/doc/html/rfc7662
|
||||
Introspect(context.Context, *Request[oidc.IntrospectionRequest]) (*Response[oidc.IntrospectionResponse], error)
|
||||
|
||||
// UserInfo handles the UserInfo endpoint and returns Claims about the authenticated End-User.
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
UserInfo(context.Context, *Request[oidc.UserInfoRequest]) (*Response[oidc.UserInfo], error)
|
||||
|
||||
// Revocation handles token revocation using an access or refresh token.
|
||||
// https://datatracker.ietf.org/doc/html/rfc7009
|
||||
Revocation(context.Context, *Request[oidc.RevocationRequest]) (*Response[struct{}], error)
|
||||
|
||||
// EndSession handles the OpenID Connect RP-Initiated Logout.
|
||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||
EndSession(context.Context, *Request[oidc.EndSessionRequest]) (*Response[struct{}], error)
|
||||
|
||||
// Keys serves the JWK set which the client can use verify signatures from the op.
|
||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata `jwks_uri` key.
|
||||
Keys(context.Context, *Request[struct{}]) (*Response[jose.JSONWebKeySet], error)
|
||||
|
||||
mustImpl()
|
||||
}
|
||||
|
||||
type UnimplementedServer struct{}
|
||||
|
||||
// UnimplementedStatusCode is the statuscode returned for methods
|
||||
// that are not yet implemented.
|
||||
// Note that this means methods in the sense of the Go interface,
|
||||
// and not http methods covered by "501 Not Implemented".
|
||||
var UnimplementedStatusCode = http.StatusNotFound
|
||||
|
||||
func unimplementedError[T any](r *Request[T]) StatusError {
|
||||
err := oidc.ErrServerError().WithDescription(fmt.Sprintf("%s not implemented on this server", r.URL.Path))
|
||||
return StatusError{
|
||||
parent: err,
|
||||
statusCode: UnimplementedStatusCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (UnimplementedServer) mustImpl() {}
|
||||
|
||||
func (UnimplementedServer) Health(_ context.Context, r *Request[struct{}]) (*Response[Status], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Ready(_ context.Context, r *Request[struct{}]) (*Response[Status], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Discovery(_ context.Context, r *Request[struct{}]) (*Response[oidc.DiscoveryConfiguration], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Authorize(_ context.Context, r *Request[oidc.AuthRequest]) (*Response[url.URL], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) DeviceAuthorization(_ context.Context, r *Request[oidc.DeviceAuthorizationRequest]) (*Response[oidc.DeviceAuthorizationResponse], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) VerifyClient(_ context.Context, r *Request[ClientCredentials]) (Client, error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) CodeExchange(_ context.Context, r *ClientRequest[oidc.AccessTokenRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r.Request)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) RefreshToken(_ context.Context, r *ClientRequest[oidc.RefreshTokenRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r.Request)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) JWTProfile(_ context.Context, r *Request[oidc.JWTProfileGrantRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) TokenExchange(_ context.Context, r *ClientRequest[oidc.TokenExchangeRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r.Request)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) ClientCredentialsExchange(_ context.Context, r *ClientRequest[oidc.ClientCredentialsRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r.Request)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) DeviceToken(_ context.Context, r *ClientRequest[oidc.DeviceAccessTokenRequest]) (*Response[oidc.AccessTokenResponse], error) {
|
||||
return nil, unimplementedError(r.Request)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Introspect(_ context.Context, r *Request[oidc.IntrospectionRequest]) (*Response[oidc.IntrospectionResponse], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) UserInfo(_ context.Context, r *Request[oidc.UserInfoRequest]) (*Response[oidc.UserInfo], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Revocation(_ context.Context, r *Request[oidc.RevocationRequest]) (*Response[struct{}], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) EndSession(_ context.Context, r *Request[oidc.EndSessionRequest]) (*Response[struct{}], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
||||
|
||||
func (UnimplementedServer) Keys(_ context.Context, r *Request[struct{}]) (*Response[jose.JSONWebKeySet], error) {
|
||||
return nil, unimplementedError(r)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue