Add login workflow until token generation (#48)
Some checks failed
/ docker-build-only (push) Failing after 33s
/ go-test (push) Failing after 1m20s

This commit is contained in:
Melora Hugues 2024-10-15 19:35:14 +02:00
parent a0849388a7
commit c71e7fa12f
18 changed files with 334 additions and 49 deletions

View file

@ -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{

View file

@ -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

View file

@ -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=

View 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}
}

View file

@ -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, &timestamp); 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}
}

View file

@ -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 {

View file

@ -0,0 +1,9 @@
package model
import "github.com/google/uuid"
type AuthCode struct {
CodeID uuid.UUID
RequestID uuid.UUID
Code string
}

View file

@ -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 {

View file

@ -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
}

View 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"
}

View 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
}

View file

@ -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) {

View file

@ -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
);

View file

@ -0,0 +1,2 @@
ALTER TABLE "auth_request_2" DROP COLUMN code_challenge;
ALTER TABLE "auth_request_2" DROP COLUMN code_challenge_method;

View 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 '';

View file

@ -0,0 +1 @@
ALTER TABLE "auth_request_2" DROP COLUMN auth_time;

View file

@ -0,0 +1 @@
ALTER TABLE "auth_request_2" ADD COLUMN auth_time timestamp;

Binary file not shown.