diff --git a/polyculeconnect.db b/polyculeconnect.db new file mode 100644 index 0000000..5b1a7bd Binary files /dev/null and b/polyculeconnect.db differ diff --git a/polyculeconnect/cmd/serve/serve.go b/polyculeconnect/cmd/serve/serve.go index 63c7415..32a48dd 100644 --- a/polyculeconnect/cmd/serve/serve.go +++ b/polyculeconnect/cmd/serve/serve.go @@ -72,6 +72,7 @@ func serve() { // - do a try-loop? // - only init when using them in a request? for _, c := range backendConfs { + logger.L.Debugf("Initializing backend %s", c.Name) b, err := client.New(context.Background(), c) if err != nil { utils.Failf("failed to init backend client: %s", err.Error()) diff --git a/polyculeconnect/go.sum b/polyculeconnect/go.sum index 6758873..99d4fb1 100644 --- a/polyculeconnect/go.sum +++ b/polyculeconnect/go.sum @@ -125,6 +125,8 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= +github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= diff --git a/polyculeconnect/internal/client/client.go b/polyculeconnect/internal/client/client.go index 0cccef7..6a933ba 100644 --- a/polyculeconnect/internal/client/client.go +++ b/polyculeconnect/internal/client/client.go @@ -3,21 +3,37 @@ package client import ( "context" "fmt" + "log/slog" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "github.com/zitadel/oidc/v3/pkg/client/rp" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" ) type OIDCClient struct { - Conf *model.Backend + conf *model.Backend + provider rp.RelyingParty + ctx context.Context + st db.Storage + l *zap.SugaredLogger } -func New(ctx context.Context, conf *model.Backend) (*OIDCClient, error) { - pr, err := rp.NewRelyingPartyOIDC(ctx, conf.OIDCConfig.Issuer, conf.OIDCConfig.ClientID, conf.OIDCConfig.ClientSecret, conf.OIDCConfig.RedirectURI, []string{}) +func New(ctx context.Context, conf *model.Backend, l *zap.SugaredLogger) (*OIDCClient, error) { + options := []rp.Option{ + rp.WithLogger(slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil))), + } + pr, err := rp.NewRelyingPartyOIDC(ctx, conf.OIDCConfig.Issuer, conf.OIDCConfig.ClientID, conf.OIDCConfig.ClientSecret, conf.OIDCConfig.RedirectURI, []string{}, options...) if err != nil { return nil, fmt.Errorf("failed to init relying party provider: %w", err) } - return &OIDCClient{Conf: conf, provider: pr}, nil + return &OIDCClient{ctx: ctx, conf: conf, provider: pr, l: l}, nil +} + +func (c *OIDCClient) toto() { + c.provider.GetDeviceAuthorizationEndpoint() } diff --git a/polyculeconnect/internal/db/authrequest/authrequest.go b/polyculeconnect/internal/db/authrequest/authrequest.go new file mode 100644 index 0000000..3506dee --- /dev/null +++ b/polyculeconnect/internal/db/authrequest/authrequest.go @@ -0,0 +1,77 @@ +package authrequest + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "github.com/google/uuid" +) + +var ErrNotFound = errors.New("backend not found") + +const authRequestRows = `"id", "client_id", "backend_id", "scopes", "redirect_uri", "state", "nonce", "response_type", "creation_time"` + +type AuthRequestDB interface { + GetAuthRequestByID(ctx context.Context, id uuid.UUID) (*model.AuthRequest, error) + CreateAuthRequest(ctx context.Context, req model.AuthRequest) error +} + +type sqlAuthRequestDB struct { + db *sql.DB +} + +func (db *sqlAuthRequestDB) GetAuthRequestByID(ctx context.Context, id uuid.UUID) (*model.AuthRequest, error) { + query := fmt.Sprintf(`SELECT %s FROM "auth_request_2" WHERE "id" = ?`, authRequestRows) + row := db.db.QueryRowContext(ctx, query, id) + + var res model.AuthRequest + var scopesStr []byte + + if err := row.Scan(&res.ID, &res.ClientID, &res.BackendID, &scopesStr, &res.RedirectURI, &res.State, &res.Nonce, &res.ResponseType, &res.CreationDate); err != nil { + return nil, fmt.Errorf("failed to get auth request from DB: %w", err) + } + if err := json.Unmarshal(scopesStr, &res.Scopes); err != nil { + return nil, fmt.Errorf("invalid format for scopes: %w", err) + } + + fmt.Println(res) + + return &res, nil +} + +func (db *sqlAuthRequestDB) CreateAuthRequest(ctx context.Context, req model.AuthRequest) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + scopesStr, err := json.Marshal(req.Scopes) + if err != nil { + return fmt.Errorf("failed to serialize scopes: %w", err) + } + + query := fmt.Sprintf(`INSERT INTO "auth_request_2" (%s) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, authRequestRows) + _, err = tx.ExecContext(ctx, query, + req.ID, req.ClientID, req.BackendID, + scopesStr, req.RedirectURI, req.State, + req.Nonce, req.ResponseType, req.CreationDate, req.AuthTime, + ) + if err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + +func New(db *sql.DB) *sqlAuthRequestDB { + return &sqlAuthRequestDB{db: db} +} diff --git a/polyculeconnect/internal/db/backend/backend.go b/polyculeconnect/internal/db/backend/backend.go index 3708442..b68f3ef 100644 --- a/polyculeconnect/internal/db/backend/backend.go +++ b/polyculeconnect/internal/db/backend/backend.go @@ -21,6 +21,7 @@ type scannable interface { type BackendDB interface { GetAllBackends(ctx context.Context) ([]*model.Backend, error) + GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) GetBackendByName(ctx context.Context, name string) (*model.Backend, error) AddBackend(ctx context.Context, newBackend *model.Backend) error @@ -50,6 +51,12 @@ func (db *sqlBackendDB) GetBackendByName(ctx context.Context, name string) (*mod return backendFromRow(row) } +func (db *sqlBackendDB) GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) { + query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "id" = ?`, backendRows) + row := db.db.QueryRowContext(ctx, query, id) + return backendFromRow(row) +} + func (db *sqlBackendDB) GetAllBackends(ctx context.Context) ([]*model.Backend, error) { rows, err := db.db.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM "backend"`, backendRows)) if err != nil { diff --git a/polyculeconnect/internal/db/base.go b/polyculeconnect/internal/db/base.go index dabfded..207a520 100644 --- a/polyculeconnect/internal/db/base.go +++ b/polyculeconnect/internal/db/base.go @@ -5,6 +5,7 @@ import ( "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authrequest" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/backend" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/client" ) @@ -13,6 +14,7 @@ type Storage interface { DB() *sql.DB ClientStorage() client.ClientDB BackendStorage() backend.BackendDB + AuthRequestStorage() authrequest.AuthRequestDB } type sqlStorage struct { @@ -31,6 +33,10 @@ func (s *sqlStorage) BackendStorage() backend.BackendDB { return backend.New(s.db) } +func (s *sqlStorage) AuthRequestStorage() authrequest.AuthRequestDB { + return authrequest.New(s.db) +} + func New(conf config.AppConfig) (Storage, error) { db, err := sql.Open("sqlite3", conf.StorageConfig.File) if err != nil { diff --git a/polyculeconnect/internal/model/authrequest.go b/polyculeconnect/internal/model/authrequest.go index 71c5f8e..07324a8 100644 --- a/polyculeconnect/internal/model/authrequest.go +++ b/polyculeconnect/internal/model/authrequest.go @@ -1,7 +1,6 @@ package model import ( - "fmt" "strings" "time" @@ -29,8 +28,10 @@ type AuthRequest struct { CodeChallengeMethod string BackendID uuid.UUID - UserID uuid.UUID - done bool + Backend *Backend + + UserID uuid.UUID + done bool } func (a AuthRequest) GetID() string { @@ -54,7 +55,7 @@ func (a AuthRequest) GetAuthTime() time.Time { } func (a AuthRequest) GetClientID() string { - return a.ClientID + return a.ID.String() // small hack since we actually need the AuthRequestID here } func (a AuthRequest) GetCodeChallenge() *oidc.CodeChallenge { @@ -97,8 +98,6 @@ func (a AuthRequest) Done() bool { } func (a *AuthRequest) FromOIDCAuthRequest(req *oidc.AuthRequest, backendID uuid.UUID) { - fmt.Println(req) - a.ID = uuid.New() a.ClientID = req.ClientID a.Scopes = strings.Split(req.Scopes.String(), " ") @@ -110,7 +109,4 @@ func (a *AuthRequest) FromOIDCAuthRequest(req *oidc.AuthRequest, backendID uuid. a.CodeChallenge = req.CodeChallenge a.CodeChallengeMethod = string(req.CodeChallengeMethod) a.BackendID = backendID - - fmt.Println(a) - } diff --git a/polyculeconnect/internal/model/client.go b/polyculeconnect/internal/model/client.go index 661aefb..f2cdda7 100644 --- a/polyculeconnect/internal/model/client.go +++ b/polyculeconnect/internal/model/client.go @@ -49,12 +49,11 @@ func (c Client) GrantTypes() []oidc.GrantType { } func (c Client) LoginURL(authRequestID string) string { - // here we have the requestID, meaning we should: - // - get the request from its ID - // - get the associated backend - // - build the correct URI to use as a redirection, which is from the backend - // - afterwards would should basically handle it as a OIDC client - return authRequestID + if c.AuthRequest == nil { + return "" // we don't have a request, let's return nothing + } + + return c.AuthRequest.Backend.OIDCConfig.Issuer } func (c Client) AccessTokenType() op.AccessTokenType { diff --git a/polyculeconnect/internal/storage/storage.go b/polyculeconnect/internal/storage/storage.go index f0c9c84..902c8a1 100644 --- a/polyculeconnect/internal/storage/storage.go +++ b/polyculeconnect/internal/storage/storage.go @@ -9,6 +9,7 @@ import ( "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" "github.com/go-jose/go-jose/v4" + "github.com/google/uuid" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" ) @@ -39,6 +40,10 @@ func (s *Storage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest, var opReq model.AuthRequest opReq.FromOIDCAuthRequest(req, selectedBackend.ID) + if err := s.LocalStorage.AuthRequestStorage().CreateAuthRequest(ctx, opReq); err != nil { + return nil, fmt.Errorf("failed to save auth request: %w", err) + } + return opReq, nil } @@ -98,13 +103,43 @@ func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) { OP storage */ -func (s *Storage) GetClientByClientID(ctx context.Context, clientID string) (op.Client, error) { - clt, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, clientID) +func (s *Storage) getClientWithDetails(ctx context.Context, authRequestID uuid.UUID) (op.Client, error) { + authRequest, err := s.LocalStorage.AuthRequestStorage().GetAuthRequestByID(ctx, authRequestID) if err != nil { - return nil, fmt.Errorf("failed to get client from local storage: %w", err) + return nil, fmt.Errorf("failed to get authRequest from local storage: %w", err) + } + backend, err := s.LocalStorage.BackendStorage().GetBackendByID(ctx, authRequest.BackendID) + if err != nil { + return nil, fmt.Errorf("failed to get associated backend from local storage: %w", err) + } + client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, authRequest.ClientID) + if err != nil { + return nil, fmt.Errorf("failed to get associated client from local storage: %w", err) } - return clt, nil + authRequest.Backend = backend + client.AuthRequest = authRequest + + return client, nil +} + +// We're cheating a bit here since we're using the authrequest to get its associated client +// but a request is always associated to a backend, and we really need both, so we have no +// choice here. I'll maybe need to have a more elegant solution later, but not choice for now +func (s *Storage) GetClientByClientID(ctx context.Context, id string) (op.Client, error) { + + authRequestID, err := uuid.Parse(id) + if err != nil { + // it's not a UUID, it means this was called using client_id, we just return the client without details + client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get client %s from local storage: %w", id, err) + } + return client, nil + } + + // we have a UUID, it means we got a requestID, so we can get all details here + return s.getClientWithDetails(ctx, authRequestID) } func (s *Storage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error { diff --git a/polyculeconnect/migrations/1_create_auth_request.down.sql b/polyculeconnect/migrations/1_create_auth_request.down.sql new file mode 100644 index 0000000..998bfa6 --- /dev/null +++ b/polyculeconnect/migrations/1_create_auth_request.down.sql @@ -0,0 +1 @@ +DROP TABLE "auth_request_2"; \ No newline at end of file diff --git a/polyculeconnect/migrations/1_create_auth_request.up.sql b/polyculeconnect/migrations/1_create_auth_request.up.sql new file mode 100644 index 0000000..534cadc --- /dev/null +++ b/polyculeconnect/migrations/1_create_auth_request.up.sql @@ -0,0 +1,11 @@ +CREATE TABLE "auth_request_2" ( + id TEXT NOT NULL PRIMARY KEY, + client_id TEXT NOT NULL, + backend_id TEXT NOT NULL, + scopes blob NOT NULL, -- list of strings, json-encoded + redirect_uri TEXT NOT NULL, + state TEXT NOT NULL, + nonce TEXT NOT NULL, + response_type TEXT NOT NULL, + CREATION_TIME timestamp NOT NULL +); \ No newline at end of file diff --git a/polyculeconnect/polyculeconnect.db b/polyculeconnect/polyculeconnect.db index 3b23f7a..0cc1857 100644 Binary files a/polyculeconnect/polyculeconnect.db and b/polyculeconnect/polyculeconnect.db differ