feat/epic-48-replace-dex #20
13 changed files with 174 additions and 23 deletions
BIN
polyculeconnect.db
Normal file
BIN
polyculeconnect.db
Normal file
Binary file not shown.
|
@ -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())
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
77
polyculeconnect/internal/db/authrequest/authrequest.go
Normal file
77
polyculeconnect/internal/db/authrequest/authrequest.go
Normal file
|
@ -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}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE "auth_request_2";
|
11
polyculeconnect/migrations/1_create_auth_request.up.sql
Normal file
11
polyculeconnect/migrations/1_create_auth_request.up.sql
Normal file
|
@ -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
|
||||
);
|
Binary file not shown.
Loading…
Reference in a new issue