Add login workflow until token generation (#48)
This commit is contained in:
parent
a0849388a7
commit
c71e7fa12f
18 changed files with 334 additions and 49 deletions
|
@ -2,6 +2,9 @@ package serve
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
@ -12,10 +15,12 @@ import (
|
|||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/client"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/middlewares"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/server"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
@ -53,9 +58,23 @@ func serve() {
|
|||
}
|
||||
|
||||
backends := map[uuid.UUID]*client.OIDCClient{}
|
||||
key := sha256.Sum256([]byte("test"))
|
||||
|
||||
st := storage.Storage{LocalStorage: userDB, InitializedBackends: backends}
|
||||
opConf := op.Config{}
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
utils.Failf("Failed to generate private key: %s", err)
|
||||
}
|
||||
signingKey := model.Key{
|
||||
PrivateKey: privateKey,
|
||||
KeyID: uuid.New(),
|
||||
SigningAlg: jose.RS256,
|
||||
}
|
||||
|
||||
st := storage.Storage{LocalStorage: userDB, InitializedBackends: backends, Key: &signingKey}
|
||||
opConf := op.Config{
|
||||
CryptoKey: key,
|
||||
CodeMethodS256: false,
|
||||
}
|
||||
slogger := slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil))
|
||||
// slogger :=
|
||||
options := []op.Option{
|
||||
|
|
|
@ -14,7 +14,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/zitadel/oidc/v3 v3.27.0
|
||||
github.com/zitadel/oidc/v3 v3.30.1
|
||||
go.uber.org/zap v1.24.0
|
||||
go.uber.org/zap/exp v0.2.0
|
||||
)
|
||||
|
@ -68,25 +68,25 @@ require (
|
|||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/rs/cors v1.11.0 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.4.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/zitadel/logging v0.6.0 // indirect
|
||||
github.com/zitadel/logging v0.6.1 // indirect
|
||||
github.com/zitadel/schema v1.3.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/api v0.150.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
|
|
|
@ -177,8 +177,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
|||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys=
|
||||
github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
@ -210,20 +210,20 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank=
|
||||
github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
|
||||
github.com/zitadel/oidc/v3 v3.27.0 h1:zeYpyRH0UcgdCjVHUYzSsqf1jbSwVMPVxYKOnRXstgU=
|
||||
github.com/zitadel/oidc/v3 v3.27.0/go.mod h1:ZwBEqSviCpJVZiYashzo53bEGRGXi7amE5Q8PpQg9IM=
|
||||
github.com/zitadel/logging v0.6.1 h1:Vyzk1rl9Kq9RCevcpX6ujUaTYFX43aa4LkvV1TvUk+Y=
|
||||
github.com/zitadel/logging v0.6.1/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
|
||||
github.com/zitadel/oidc/v3 v3.30.1 h1:CCi9qDjleuRYbECfUoVKrrN97KdheNCHAs33X8XnIRg=
|
||||
github.com/zitadel/oidc/v3 v3.30.1/go.mod h1:N5p02vx+mLUwf+WFNpDsNp+8DS8+jlgFBwpz7NIQjrg=
|
||||
github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
|
||||
github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
|
@ -265,16 +265,16 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -303,8 +303,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
62
polyculeconnect/internal/db/authcode/authcode.go
Normal file
62
polyculeconnect/internal/db/authcode/authcode.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package authcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("auth code not found")
|
||||
|
||||
type AuthCodeDB interface {
|
||||
GetAuthCodeByCode(ctx context.Context, code string) (*model.AuthCode, error)
|
||||
CreateAuthCode(ctx context.Context, code model.AuthCode) error
|
||||
}
|
||||
|
||||
type sqlAuthCodeDB struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (db *sqlAuthCodeDB) CreateAuthCode(ctx context.Context, code model.AuthCode) error {
|
||||
logger.L.Debugf("Creating auth code for request %s", code.RequestID)
|
||||
tx, err := db.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
query := `INSERT INTO "auth_code_2" ("id", "auth_request_id", "code") VALUES ($1, $2, $3)`
|
||||
_, err = tx.ExecContext(ctx, query, code.CodeID, code.RequestID, code.Code)
|
||||
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 (db *sqlAuthCodeDB) GetAuthCodeByCode(ctx context.Context, code string) (*model.AuthCode, error) {
|
||||
logger.L.Debugf("Getting auth code %s from DB", code)
|
||||
query := `SELECT "id", "auth_request_id", "code" FROM "auth_code_2" WHERE "code" = ?`
|
||||
row := db.db.QueryRowContext(ctx, query, code)
|
||||
|
||||
var res model.AuthCode
|
||||
if err := row.Scan(&res.CodeID, &res.RequestID, &res.Code); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read row from DB: %w", err)
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func New(db *sql.DB) AuthCodeDB {
|
||||
return &sqlAuthCodeDB{db: db}
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
|
||||
|
@ -14,12 +15,13 @@ import (
|
|||
|
||||
var ErrNotFound = errors.New("backend not found")
|
||||
|
||||
const authRequestRows = `"id", "client_id", "backend_id", "scopes", "redirect_uri", "state", "nonce", "response_type", "creation_time", "done"`
|
||||
const authRequestRows = `"id", "client_id", "backend_id", "scopes", "redirect_uri", "state", "nonce", "response_type", "creation_time", "done", "code_challenge", "code_challenge_method", "auth_time"`
|
||||
|
||||
type AuthRequestDB interface {
|
||||
GetAuthRequestByID(ctx context.Context, id uuid.UUID) (*model.AuthRequest, error)
|
||||
CreateAuthRequest(ctx context.Context, req model.AuthRequest) error
|
||||
ValidateAuthRequest(ctx context.Context, reqID uuid.UUID) error
|
||||
DeleteAuthRequest(ctx context.Context, reqID uuid.UUID) error
|
||||
}
|
||||
|
||||
type sqlAuthRequestDB struct {
|
||||
|
@ -34,10 +36,14 @@ func (db *sqlAuthRequestDB) GetAuthRequestByID(ctx context.Context, id uuid.UUID
|
|||
var res model.AuthRequest
|
||||
var scopesStr []byte
|
||||
|
||||
fmt.Println(query)
|
||||
if err := row.Scan(&res.ID, &res.ClientID, &res.BackendID, &scopesStr, &res.RedirectURI, &res.State, &res.Nonce, &res.ResponseType, &res.CreationDate, &res.DoneVal); err != nil {
|
||||
var timestamp *time.Time
|
||||
|
||||
if err := row.Scan(&res.ID, &res.ClientID, &res.BackendID, &scopesStr, &res.RedirectURI, &res.State, &res.Nonce, &res.ResponseType, &res.CreationDate, &res.DoneVal, &res.CodeChallenge, &res.CodeChallengeMethod, ×tamp); err != nil {
|
||||
return nil, fmt.Errorf("failed to get auth request from DB: %w", err)
|
||||
}
|
||||
if timestamp != nil {
|
||||
res.AuthTime = *timestamp
|
||||
}
|
||||
if err := json.Unmarshal(scopesStr, &res.Scopes); err != nil {
|
||||
return nil, fmt.Errorf("invalid format for scopes: %w", err)
|
||||
}
|
||||
|
@ -59,11 +65,12 @@ func (db *sqlAuthRequestDB) CreateAuthRequest(ctx context.Context, req model.Aut
|
|||
}
|
||||
|
||||
// TODO: when the old table is done, rename into auth_request
|
||||
query := fmt.Sprintf(`INSERT INTO "auth_request_2" (%s) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, authRequestRows)
|
||||
query := fmt.Sprintf(`INSERT INTO "auth_request_2" (%s) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NULL)`, authRequestRows)
|
||||
_, err = tx.ExecContext(ctx, query,
|
||||
req.ID, req.ClientID, req.BackendID,
|
||||
scopesStr, req.RedirectURI, req.State,
|
||||
req.Nonce, req.ResponseType, req.CreationDate, false,
|
||||
req.CodeChallenge, req.CodeChallengeMethod,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert in DB: %w", err)
|
||||
|
@ -84,7 +91,7 @@ func (db *sqlAuthRequestDB) ValidateAuthRequest(ctx context.Context, reqID uuid.
|
|||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
res, err := tx.ExecContext(ctx, `UPDATE "auth_request_2" SET done = true WHERE id = $1`, reqID.String())
|
||||
res, err := tx.ExecContext(ctx, `UPDATE "auth_request_2" SET done = true, auth_time = $1 WHERE id = $2`, time.Now().UTC(), reqID.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update in DB: %w", err)
|
||||
}
|
||||
|
@ -103,6 +110,24 @@ func (db *sqlAuthRequestDB) ValidateAuthRequest(ctx context.Context, reqID uuid.
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *sqlAuthRequestDB) DeleteAuthRequest(ctx context.Context, reqID uuid.UUID) error {
|
||||
logger.L.Debugf("Deleting auth request: %s", reqID)
|
||||
tx, err := db.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
_, err = tx.ExecContext(ctx, `DELETE FROM "auth_request_2" WHERE id = $1`, reqID.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete auth request: %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}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
|
||||
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authcode"
|
||||
"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"
|
||||
|
@ -15,6 +16,7 @@ type Storage interface {
|
|||
ClientStorage() client.ClientDB
|
||||
BackendStorage() backend.BackendDB
|
||||
AuthRequestStorage() authrequest.AuthRequestDB
|
||||
AuthCodeStorage() authcode.AuthCodeDB
|
||||
}
|
||||
|
||||
type sqlStorage struct {
|
||||
|
@ -37,6 +39,10 @@ func (s *sqlStorage) AuthRequestStorage() authrequest.AuthRequestDB {
|
|||
return authrequest.New(s.db)
|
||||
}
|
||||
|
||||
func (s *sqlStorage) AuthCodeStorage() authcode.AuthCodeDB {
|
||||
return authcode.New(s.db)
|
||||
}
|
||||
|
||||
func New(conf config.AppConfig) (Storage, error) {
|
||||
db, err := sql.Open("sqlite3", conf.StorageConfig.File)
|
||||
if err != nil {
|
||||
|
|
9
polyculeconnect/internal/model/authcode.go
Normal file
9
polyculeconnect/internal/model/authcode.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package model
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type AuthCode struct {
|
||||
CodeID uuid.UUID
|
||||
RequestID uuid.UUID
|
||||
Code string
|
||||
}
|
|
@ -56,7 +56,7 @@ func (a AuthRequest) GetAuthTime() time.Time {
|
|||
}
|
||||
|
||||
func (a AuthRequest) GetClientID() string {
|
||||
return a.ID.String() // small hack since we actually need the AuthRequestID here
|
||||
return a.ClientID
|
||||
}
|
||||
|
||||
func (a AuthRequest) GetCodeChallenge() *oidc.CodeChallenge {
|
||||
|
|
|
@ -39,7 +39,7 @@ func (c Client) ApplicationType() op.ApplicationType {
|
|||
}
|
||||
|
||||
func (c Client) AuthMethod() oidc.AuthMethod {
|
||||
return oidc.AuthMethodNone
|
||||
return oidc.AuthMethodBasic
|
||||
}
|
||||
|
||||
func (c Client) ResponseTypes() []oidc.ResponseType {
|
||||
|
@ -47,13 +47,13 @@ func (c Client) ResponseTypes() []oidc.ResponseType {
|
|||
}
|
||||
|
||||
func (c Client) GrantTypes() []oidc.GrantType {
|
||||
return []oidc.GrantType{oidc.GrantTypeCode}
|
||||
return []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeRefreshToken, oidc.GrantTypeTokenExchange}
|
||||
}
|
||||
|
||||
// LoginURL returns the login URL for a given client app and auth request.
|
||||
// This login url should be the authorization URL for the selected OIDC backend
|
||||
func (c Client) LoginURL(authRequestID string) string {
|
||||
if c.AuthRequest == nil {
|
||||
if authRequestID == "" {
|
||||
return "" // we don't have a request, let's return nothing
|
||||
}
|
||||
|
||||
|
|
57
polyculeconnect/internal/model/signingkey.go
Normal file
57
polyculeconnect/internal/model/signingkey.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"strings"
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SigningKey struct {
|
||||
PrivateKey *rsa.PrivateKey
|
||||
KeyID uuid.UUID
|
||||
Algorithm jose.SignatureAlgorithm
|
||||
}
|
||||
|
||||
func (k *SigningKey) ID() string {
|
||||
return strings.ReplaceAll(k.KeyID.String(), "-", "")
|
||||
}
|
||||
|
||||
func (k *SigningKey) SignatureAlgorithm() jose.SignatureAlgorithm {
|
||||
return k.Algorithm
|
||||
}
|
||||
|
||||
func (k *SigningKey) Key() any {
|
||||
return k.PrivateKey
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
PrivateKey *rsa.PrivateKey
|
||||
KeyID uuid.UUID
|
||||
SigningAlg jose.SignatureAlgorithm
|
||||
}
|
||||
|
||||
func (k *Key) SigningKey() *SigningKey {
|
||||
return &SigningKey{
|
||||
PrivateKey: k.PrivateKey,
|
||||
KeyID: k.KeyID,
|
||||
Algorithm: k.SigningAlg,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Key) ID() string {
|
||||
return strings.ReplaceAll(k.KeyID.String(), "-", "")
|
||||
}
|
||||
|
||||
func (k *Key) Algorithm() jose.SignatureAlgorithm {
|
||||
return k.SigningAlg
|
||||
}
|
||||
|
||||
func (k *Key) Key() any {
|
||||
return &k.PrivateKey.PublicKey
|
||||
}
|
||||
|
||||
func (k *Key) Use() string {
|
||||
return "sig"
|
||||
}
|
21
polyculeconnect/internal/model/token.go
Normal file
21
polyculeconnect/internal/model/token.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
ID uuid.UUID
|
||||
RefreshTokenID uuid.UUID
|
||||
Expiration time.Time
|
||||
Subjet string
|
||||
Audiences []string
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
type RefreshToken struct {
|
||||
ID uuid.UUID
|
||||
AuthTime time.Time
|
||||
}
|
|
@ -24,6 +24,7 @@ func ErrNotImplemented(name string) error {
|
|||
type Storage struct {
|
||||
LocalStorage db.Storage
|
||||
InitializedBackends map[uuid.UUID]*client.OIDCClient
|
||||
Key *model.Key
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -33,7 +34,7 @@ func (s *Storage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest,
|
|||
|
||||
// userID should normally be an empty string (to verify), we don't get it in our workflow from what I saw
|
||||
// TODO: check this is indeed not needed / never present
|
||||
logger.L.Debug("Creating a new auth request")
|
||||
logger.L.Debugf("Creating a new auth request")
|
||||
|
||||
// validate that the connector is correct
|
||||
backendName, ok := stringFromCtx(ctx, "backendName")
|
||||
|
@ -69,24 +70,90 @@ func (s *Storage) AuthRequestByID(ctx context.Context, requestID string) (op.Aut
|
|||
}
|
||||
|
||||
func (s *Storage) AuthRequestByCode(ctx context.Context, requestCode string) (op.AuthRequest, error) {
|
||||
return nil, ErrNotImplemented("AuthRequestByCode")
|
||||
logger.L.Debugf("Getting auth request from code %s", requestCode)
|
||||
|
||||
authCode, err := s.LocalStorage.AuthCodeStorage().GetAuthCodeByCode(ctx, requestCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get auth code from DB: %w", err)
|
||||
}
|
||||
|
||||
return s.LocalStorage.AuthRequestStorage().GetAuthRequestByID(ctx, authCode.RequestID)
|
||||
}
|
||||
|
||||
func (s *Storage) SaveAuthCode(ctx context.Context, id string, code string) error {
|
||||
logger.L.Debugf("Saving auth code %s for request %s", code, id)
|
||||
return ErrNotImplemented("SaveAuthCode")
|
||||
|
||||
requestID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid requestID %s: %w", requestID, err)
|
||||
}
|
||||
|
||||
codeID := uuid.New()
|
||||
|
||||
savedCode := model.AuthCode{
|
||||
CodeID: codeID,
|
||||
RequestID: requestID,
|
||||
Code: code,
|
||||
}
|
||||
return s.LocalStorage.AuthCodeStorage().CreateAuthCode(ctx, savedCode)
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||
return ErrNotImplemented("DeleteAuthRequest")
|
||||
return nil // don't delete it for now, it seems we might need it????? (cc dex)
|
||||
// reqID, err := uuid.Parse(id)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("invalid id format: %w", err)
|
||||
// }
|
||||
// return s.LocalStorage.AuthRequestStorage().DeleteAuthRequest(ctx, reqID)
|
||||
}
|
||||
|
||||
func (s *Storage) CreateAccessToken(ctx context.Context, req op.TokenRequest) (accessTokenID string, expiration time.Time, err error) {
|
||||
return "", time.Time{}, ErrNotImplemented("CreateAccessToken")
|
||||
accessTokenUUID := uuid.New()
|
||||
|
||||
// we are expecting our own request model
|
||||
authRequest, ok := req.(*model.AuthRequest)
|
||||
if !ok {
|
||||
return "", time.Time{}, errors.New("failed to parse auth request")
|
||||
}
|
||||
|
||||
authTime := authRequest.AuthTime.UTC()
|
||||
expiration = authTime.Add(5 * time.Minute)
|
||||
|
||||
// token := model.Token{
|
||||
// ID: accessTokenUUID,
|
||||
// RefreshTokenID: refreshTokenUUID,
|
||||
// Expiration: authTime.Add(5 * time.Minute),
|
||||
// Subjet: request.GetSubject(),
|
||||
// Audiences: request.GetAudience(),
|
||||
// Scopes: request.GetScopes(),
|
||||
// }
|
||||
|
||||
return accessTokenUUID.String(), expiration, nil
|
||||
}
|
||||
|
||||
func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error) {
|
||||
return "", "", time.Time{}, ErrNotImplemented("CreateAccessAndRefreshTokens")
|
||||
accessTokenUUID := uuid.New()
|
||||
refreshTokenUUID := uuid.New()
|
||||
|
||||
// we are expecting our own request model
|
||||
authRequest, ok := request.(*model.AuthRequest)
|
||||
if !ok {
|
||||
return "", "", time.Time{}, errors.New("failed to parse auth request")
|
||||
}
|
||||
|
||||
authTime := authRequest.AuthTime.UTC()
|
||||
expiration = authTime.Add(5 * time.Minute)
|
||||
|
||||
// token := model.Token{
|
||||
// ID: accessTokenUUID,
|
||||
// RefreshTokenID: refreshTokenUUID,
|
||||
// Expiration: authTime.Add(5 * time.Minute),
|
||||
// Subjet: request.GetSubject(),
|
||||
// Audiences: request.GetAudience(),
|
||||
// Scopes: request.GetScopes(),
|
||||
// }
|
||||
|
||||
return accessTokenUUID.String(), refreshTokenUUID.String(), expiration, nil
|
||||
}
|
||||
|
||||
func (s *Storage) TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (op.RefreshTokenRequest, error) {
|
||||
|
@ -106,7 +173,7 @@ func (s *Storage) GetRefreshTokenInfo(ctx context.Context, clientID string, stok
|
|||
}
|
||||
|
||||
func (s *Storage) SigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
return nil, ErrNotImplemented("SigningKey")
|
||||
return s.Key.SigningKey(), nil
|
||||
}
|
||||
|
||||
func (s *Storage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
|
@ -114,7 +181,7 @@ func (s *Storage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgo
|
|||
}
|
||||
|
||||
func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) {
|
||||
return nil, ErrNotImplemented("KeySet")
|
||||
return []op.Key{s.Key}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -169,11 +236,23 @@ func (s *Storage) GetClientByClientID(ctx context.Context, id string) (op.Client
|
|||
}
|
||||
|
||||
func (s *Storage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error {
|
||||
return ErrNotImplemented("AuthorizeClientIDSecret")
|
||||
logger.L.Debugf("Validating client secret %s for client %s", clientSecret, clientID)
|
||||
|
||||
client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, clientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if client.Secret != clientSecret {
|
||||
return errors.New("invalid secret")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) SetUserinfoFromScopes(ctx context.Context, userinfo *oidc.UserInfo, userID, clientID string, scopes []string) error {
|
||||
return ErrNotImplemented("SetUserinfoFromScopes")
|
||||
// we'll use FromRequest instead
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserInfo, tokenID, subject, origin string) error {
|
||||
|
@ -185,7 +264,8 @@ func (s *Storage) SetIntrospectionFromToken(ctx context.Context, userinfo *oidc.
|
|||
}
|
||||
|
||||
func (s *Storage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error) {
|
||||
return nil, ErrNotImplemented("GetPrivateClaimsFromScopes")
|
||||
// For now, let's just return nothing, we don't want to add any private scope
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetKeyByIDAndClientID(ctx context.Context, keyID, clientID string) (*jose.JSONWebKey, error) {
|
||||
|
|
|
@ -7,5 +7,5 @@ CREATE TABLE "auth_request_2" (
|
|||
state TEXT NOT NULL,
|
||||
nonce TEXT NOT NULL,
|
||||
response_type TEXT NOT NULL,
|
||||
CREATION_TIME timestamp NOT NULL
|
||||
creation_time timestamp NOT NULL
|
||||
);
|
2
polyculeconnect/migrations/4_add_code_challenge.down.sql
Normal file
2
polyculeconnect/migrations/4_add_code_challenge.down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "auth_request_2" DROP COLUMN code_challenge;
|
||||
ALTER TABLE "auth_request_2" DROP COLUMN code_challenge_method;
|
2
polyculeconnect/migrations/4_add_code_challenge.up.sql
Normal file
2
polyculeconnect/migrations/4_add_code_challenge.up.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "auth_request_2" ADD COLUMN code_challenge STRING NOT NULL DEFAULT '';
|
||||
ALTER TABLE "auth_request_2" ADD COLUMN code_challenge_method STRING NOT NULL DEFAULT '';
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "auth_request_2" DROP COLUMN auth_time;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "auth_request_2" ADD COLUMN auth_time timestamp;
|
Binary file not shown.
Loading…
Reference in a new issue