diff --git a/.env.example b/.env.example index 100b0e9d..4f1c3c31 100644 --- a/.env.example +++ b/.env.example @@ -206,6 +206,8 @@ TINYAUTH_LDAP_ADDRESS= TINYAUTH_LDAP_BINDDN= # Bind password for LDAP authentication. TINYAUTH_LDAP_BINDPASSWORD= +# Path to the Bind password. +TINYAUTH_LDAP_BINDPASSWORDFILE= # Base DN for LDAP searches. TINYAUTH_LDAP_BASEDN= # Allow insecure LDAP connections. diff --git a/gen/sqlc-wrapper/sqlc_wrapper.go b/gen/sqlc-wrapper/sqlc_wrapper.go index a7a75eb4..79576ec6 100644 --- a/gen/sqlc-wrapper/sqlc_wrapper.go +++ b/gen/sqlc-wrapper/sqlc_wrapper.go @@ -67,15 +67,24 @@ func run() error { Overlay: map[string][]byte{outPath: stub}, } - driverTypePkg, err := loadOnePkg(cfg, *driverPkg) + repoPkgPath := parentPkg(*driverPkg) + + pkgs, err := loadMultiplePkgs(cfg, *driverPkg, repoPkgPath) + if err != nil { - return fmt.Errorf("load driver package: %w", err) + return fmt.Errorf("load packages: %w", err) } - repoPkgPath := parentPkg(*driverPkg) - repoTypePkg, err := loadOnePkg(cfg, repoPkgPath) - if err != nil { - return fmt.Errorf("load repo package: %w", err) + driverTypePkg, ok := pkgs[*driverPkg] + + if !ok { + return fmt.Errorf("driver package %s not found in loaded packages", *driverPkg) + } + + repoTypePkg, ok := pkgs[repoPkgPath] + + if !ok { + return fmt.Errorf("repository package %s not found in loaded packages", repoPkgPath) } if err := validateStructShapes(driverTypePkg, repoTypePkg); err != nil { @@ -106,25 +115,25 @@ func run() error { return nil } -// loadOnePkg loads a single package via cfg and returns its *types.Package, -// or an error if the package fails to load or has type errors. -func loadOnePkg(cfg *packages.Config, importPath string) (*types.Package, error) { - pkgs, err := packages.Load(cfg, importPath) +// loadMultiplePkgs loads multiple packages via cfg and returns a map of import path → *types.Package, +// or an error if any package fails to load or has type errors. +func loadMultiplePkgs(cfg *packages.Config, importPaths ...string) (map[string]*types.Package, error) { + pkgs, err := packages.Load(cfg, importPaths...) if err != nil { - return nil, fmt.Errorf("load %s: %w", importPath, err) + return nil, fmt.Errorf("load %v: %w", importPaths, err) } - if len(pkgs) != 1 { - return nil, fmt.Errorf("expected 1 package for %s, got %d", importPath, len(pkgs)) - } - pkg := pkgs[0] - if len(pkg.Errors) > 0 { - msgs := make([]string, len(pkg.Errors)) - for i, e := range pkg.Errors { - msgs[i] = e.Error() + out := make(map[string]*types.Package) + for _, pkg := range pkgs { + if len(pkg.Errors) > 0 { + msgs := make([]string, len(pkg.Errors)) + for i, e := range pkg.Errors { + msgs[i] = e.Error() + } + return nil, fmt.Errorf("package %s has errors:\n %s", pkg.PkgPath, strings.Join(msgs, "\n ")) } - return nil, fmt.Errorf("package %s has errors:\n %s", importPath, strings.Join(msgs, "\n ")) + out[pkg.PkgPath] = pkg.Types } - return pkg.Types, nil + return out, nil } // parentPkg returns the parent import path (everything before the last /). diff --git a/internal/assets/migrations/postgres/000003_oidc_consent.up.sql b/internal/assets/migrations/postgres/000003_oidc_consent.up.sql new file mode 100644 index 00000000..6aa84ed5 --- /dev/null +++ b/internal/assets/migrations/postgres/000003_oidc_consent.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "oidc_consent" ( + "uuid" TEXT NOT NULL UNIQUE PRIMARY KEY, + "client_id" TEXT NOT NULL, + "scopes" TEXT NOT NULL, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/internal/assets/migrations/postgres/000003_oidc_consnet.down.sql b/internal/assets/migrations/postgres/000003_oidc_consnet.down.sql new file mode 100644 index 00000000..2dae02be --- /dev/null +++ b/internal/assets/migrations/postgres/000003_oidc_consnet.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "oidc_consent"; diff --git a/internal/assets/migrations/sqlite/000011_oidc_consent.down.sql b/internal/assets/migrations/sqlite/000011_oidc_consent.down.sql new file mode 100644 index 00000000..2dae02be --- /dev/null +++ b/internal/assets/migrations/sqlite/000011_oidc_consent.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "oidc_consent"; diff --git a/internal/assets/migrations/sqlite/000011_oidc_consent.up.sql b/internal/assets/migrations/sqlite/000011_oidc_consent.up.sql new file mode 100644 index 00000000..0fc41cf8 --- /dev/null +++ b/internal/assets/migrations/sqlite/000011_oidc_consent.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS "oidc_consent" ( + "uuid" TEXT NOT NULL UNIQUE PRIMARY KEY, + "client_id" TEXT NOT NULL, + "scopes" TEXT NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/internal/repository/memory/memory_test.go b/internal/repository/memory/memory_test.go index 558ed234..373d68f5 100644 --- a/internal/repository/memory/memory_test.go +++ b/internal/repository/memory/memory_test.go @@ -277,6 +277,78 @@ func TestMemoryStore(t *testing.T) { assert.NoError(t, err) }, }, + { + description: "Create and get OIDC consent", + run: func(t *testing.T, s repository.Store) { + consent, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{ + UUID: "uuid-1", + ClientID: "client-1", + Scopes: "openid profile", + }) + require.NoError(t, err) + assert.Equal(t, "uuid-1", consent.UUID) + assert.Equal(t, "client-1", consent.ClientID) + assert.Equal(t, "openid profile", consent.Scopes) + + got, err := s.GetOIDCConsentByUUID(ctx, "uuid-1") + require.NoError(t, err) + assert.Equal(t, consent, got) + }, + }, + { + description: "Get OIDC consent by UUID not found", + run: func(t *testing.T, s repository.Store) { + _, err := s.GetOIDCConsentByUUID(ctx, "missing") + assert.ErrorIs(t, err, repository.ErrNotFound) + }, + }, + { + description: "Create OIDC consent unique UUID constraint", + run: func(t *testing.T, s repository.Store) { + _, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"}) + require.NoError(t, err) + + _, err = s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-2", Scopes: "profile"}) + assert.ErrorContains(t, err, "UNIQUE constraint failed: oidc_consent.uuid") + }, + }, + { + description: "Update OIDC consent", + run: func(t *testing.T, s repository.Store) { + _, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"}) + require.NoError(t, err) + + updated, err := s.UpdateOIDCConsent(ctx, repository.UpdateOIDCConsentParams{ + UUID: "uuid-1", + Scopes: "profile email", + }) + require.NoError(t, err) + assert.Equal(t, "profile email", updated.Scopes) + + got, err := s.GetOIDCConsentByUUID(ctx, "uuid-1") + require.NoError(t, err) + assert.Equal(t, updated, got) + }, + }, + { + description: "Update OIDC consent not found", + run: func(t *testing.T, s repository.Store) { + _, err := s.UpdateOIDCConsent(ctx, repository.UpdateOIDCConsentParams{UUID: "missing"}) + assert.ErrorIs(t, err, repository.ErrNotFound) + }, + }, + { + description: "Delete OIDC consent by UUID", + run: func(t *testing.T, s repository.Store) { + _, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"}) + require.NoError(t, err) + + require.NoError(t, s.DeleteOIDCConsentByUUID(ctx, "uuid-1")) + + _, err = s.GetOIDCConsentByUUID(ctx, "uuid-1") + assert.ErrorIs(t, err, repository.ErrNotFound) + }, + }, } for _, test := range tests { diff --git a/internal/repository/memory/oidc_queries.go b/internal/repository/memory/oidc_queries.go index 1ee81c8b..70728978 100644 --- a/internal/repository/memory/oidc_queries.go +++ b/internal/repository/memory/oidc_queries.go @@ -94,3 +94,47 @@ func (s *Store) DeleteExpiredOIDCSessions(_ context.Context, arg repository.Dele } return nil } + +func (s *Store) CreateOIDCConsent(_ context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.oidcConsent[arg.UUID]; ok { + return repository.OidcConsent{}, fmt.Errorf("UNIQUE constraint failed: oidc_consent.uuid") + } + consent := repository.OidcConsent{ + UUID: arg.UUID, + ClientID: arg.ClientID, + Scopes: arg.Scopes, + } + s.oidcConsent[arg.UUID] = consent + return consent, nil +} + +func (s *Store) GetOIDCConsentByUUID(_ context.Context, uuid string) (repository.OidcConsent, error) { + s.mu.RLock() + defer s.mu.RUnlock() + consent, ok := s.oidcConsent[uuid] + if !ok { + return repository.OidcConsent{}, repository.ErrNotFound + } + return consent, nil +} + +func (s *Store) UpdateOIDCConsent(_ context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) { + s.mu.Lock() + defer s.mu.Unlock() + consent, ok := s.oidcConsent[arg.UUID] + if !ok { + return repository.OidcConsent{}, repository.ErrNotFound + } + consent.Scopes = arg.Scopes + s.oidcConsent[arg.UUID] = consent + return consent, nil +} + +func (s *Store) DeleteOIDCConsentByUUID(_ context.Context, uuid string) error { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.oidcConsent, uuid) + return nil +} diff --git a/internal/repository/memory/store.go b/internal/repository/memory/store.go index 684ddeb3..ec7fd8db 100644 --- a/internal/repository/memory/store.go +++ b/internal/repository/memory/store.go @@ -12,6 +12,7 @@ type Store struct { mu sync.RWMutex sessions map[string]repository.Session oidcSessions map[string]repository.OidcSession + oidcConsent map[string]repository.OidcConsent } // New returns a new empty in-memory Store. @@ -19,5 +20,6 @@ func New() repository.Store { return &Store{ sessions: make(map[string]repository.Session), oidcSessions: make(map[string]repository.OidcSession), + oidcConsent: make(map[string]repository.OidcConsent), } } diff --git a/internal/repository/models.go b/internal/repository/models.go index 39538a00..1d77a5fe 100644 --- a/internal/repository/models.go +++ b/internal/repository/models.go @@ -1,8 +1,18 @@ package repository +import "time" + // Shared model and parameter types for all storage drivers. // sqlc-generated driver packages use these via the conversion layer in their store.go. +type OidcConsent struct { + UUID string + ClientID string + Scopes string + CreatedAt time.Time + UpdatedAt time.Time +} + type Session struct { UUID string Username string @@ -84,3 +94,14 @@ type DeleteExpiredOIDCSessionsParams struct { TokenExpiresAt int64 RefreshTokenExpiresAt int64 } + +type CreateOIDCConsentParams struct { + UUID string + ClientID string + Scopes string +} + +type UpdateOIDCConsentParams struct { + Scopes string + UUID string +} diff --git a/internal/repository/postgres/models.go b/internal/repository/postgres/models.go index f957e1fd..a214908d 100644 --- a/internal/repository/postgres/models.go +++ b/internal/repository/postgres/models.go @@ -4,6 +4,18 @@ package postgres +import ( + "time" +) + +type OidcConsent struct { + UUID string + ClientID string + Scopes string + CreatedAt time.Time + UpdatedAt time.Time +} + type OidcSession struct { Sub string AccessTokenHash string diff --git a/internal/repository/postgres/oidc_queries.sql.go b/internal/repository/postgres/oidc_queries.sql.go index b5b9789c..363dacb2 100644 --- a/internal/repository/postgres/oidc_queries.sql.go +++ b/internal/repository/postgres/oidc_queries.sql.go @@ -9,6 +9,36 @@ import ( "context" ) +const createOIDCConsent = `-- name: CreateOIDCConsent :one +INSERT INTO "oidc_consent" ( + "uuid", + "client_id", + "scopes" +) VALUES ( + $1, $2, $3 +) +RETURNING uuid, client_id, scopes, created_at, updated_at +` + +type CreateOIDCConsentParams struct { + UUID string + ClientID string + Scopes string +} + +func (q *Queries) CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, createOIDCConsent, arg.UUID, arg.ClientID, arg.Scopes) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const createOIDCSession = `-- name: CreateOIDCSession :one INSERT INTO "oidc_sessions" ( "sub", @@ -80,6 +110,16 @@ func (q *Queries) DeleteExpiredOIDCSessions(ctx context.Context, arg DeleteExpir return err } +const deleteOIDCConsentByUUID = `-- name: DeleteOIDCConsentByUUID :exec +DELETE FROM "oidc_consent" +WHERE "uuid" = $1 +` + +func (q *Queries) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error { + _, err := q.db.ExecContext(ctx, deleteOIDCConsentByUUID, uuid) + return err +} + const deleteOIDCSessionBySub = `-- name: DeleteOIDCSessionBySub :exec DELETE FROM "oidc_sessions" WHERE "sub" = $1 @@ -90,6 +130,24 @@ func (q *Queries) DeleteOIDCSessionBySub(ctx context.Context, sub string) error return err } +const getOIDCConsentByUUID = `-- name: GetOIDCConsentByUUID :one +SELECT uuid, client_id, scopes, created_at, updated_at FROM "oidc_consent" +WHERE "uuid" = $1 +` + +func (q *Queries) GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, getOIDCConsentByUUID, uuid) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const getOIDCSessionByAccessTokenHash = `-- name: GetOIDCSessionByAccessTokenHash :one SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce, userinfo_json FROM "oidc_sessions" WHERE "access_token_hash" = $1 @@ -156,6 +214,32 @@ func (q *Queries) GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSess return i, err } +const updateOIDCConsent = `-- name: UpdateOIDCConsent :one +UPDATE "oidc_consent" SET + "scopes" = $1, + "updated_at" = CURRENT_TIMESTAMP +WHERE "uuid" = $2 +RETURNING uuid, client_id, scopes, created_at, updated_at +` + +type UpdateOIDCConsentParams struct { + Scopes string + UUID string +} + +func (q *Queries) UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, updateOIDCConsent, arg.Scopes, arg.UUID) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const updateOIDCSession = `-- name: UpdateOIDCSession :one UPDATE "oidc_sessions" SET "access_token_hash" = $1, diff --git a/internal/repository/postgres/store.go b/internal/repository/postgres/store.go index b3e79c80..719b3118 100644 --- a/internal/repository/postgres/store.go +++ b/internal/repository/postgres/store.go @@ -32,6 +32,14 @@ func mapErr(err error) error { return err } +func (s *Store) CreateOIDCConsent(ctx context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) { + r, err := s.q.CreateOIDCConsent(ctx, CreateOIDCConsentParams(arg)) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) CreateOIDCSession(ctx context.Context, arg repository.CreateOIDCSessionParams) (repository.OidcSession, error) { r, err := s.q.CreateOIDCSession(ctx, CreateOIDCSessionParams(arg)) if err != nil { @@ -56,6 +64,10 @@ func (s *Store) DeleteExpiredSessions(ctx context.Context, expiry int64) error { return mapErr(s.q.DeleteExpiredSessions(ctx, expiry)) } +func (s *Store) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error { + return mapErr(s.q.DeleteOIDCConsentByUUID(ctx, uuid)) +} + func (s *Store) DeleteOIDCSessionBySub(ctx context.Context, sub string) error { return mapErr(s.q.DeleteOIDCSessionBySub(ctx, sub)) } @@ -64,6 +76,14 @@ func (s *Store) DeleteSession(ctx context.Context, uuid string) error { return mapErr(s.q.DeleteSession(ctx, uuid)) } +func (s *Store) GetOIDCConsentByUUID(ctx context.Context, uuid string) (repository.OidcConsent, error) { + r, err := s.q.GetOIDCConsentByUUID(ctx, uuid) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) GetOIDCSessionByAccessTokenHash(ctx context.Context, accessTokenHash string) (repository.OidcSession, error) { r, err := s.q.GetOIDCSessionByAccessTokenHash(ctx, accessTokenHash) if err != nil { @@ -96,6 +116,14 @@ func (s *Store) GetSession(ctx context.Context, uuid string) (repository.Session return repository.Session(r), nil } +func (s *Store) UpdateOIDCConsent(ctx context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) { + r, err := s.q.UpdateOIDCConsent(ctx, UpdateOIDCConsentParams(arg)) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) UpdateOIDCSession(ctx context.Context, arg repository.UpdateOIDCSessionParams) (repository.OidcSession, error) { r, err := s.q.UpdateOIDCSession(ctx, UpdateOIDCSessionParams(arg)) if err != nil { diff --git a/internal/repository/sqlite/models.go b/internal/repository/sqlite/models.go index 2ced8a2b..ca4a524c 100644 --- a/internal/repository/sqlite/models.go +++ b/internal/repository/sqlite/models.go @@ -4,6 +4,18 @@ package sqlite +import ( + "time" +) + +type OidcConsent struct { + UUID string + ClientID string + Scopes string + CreatedAt time.Time + UpdatedAt time.Time +} + type OidcSession struct { Sub string AccessTokenHash string diff --git a/internal/repository/sqlite/oidc_queries.sql.go b/internal/repository/sqlite/oidc_queries.sql.go index a5aa08a8..7f7c267b 100644 --- a/internal/repository/sqlite/oidc_queries.sql.go +++ b/internal/repository/sqlite/oidc_queries.sql.go @@ -9,6 +9,36 @@ import ( "context" ) +const createOIDCConsent = `-- name: CreateOIDCConsent :one +INSERT INTO "oidc_consent" ( + "uuid", + "client_id", + "scopes" +) VALUES ( + ?, ?, ? +) +RETURNING uuid, client_id, scopes, created_at, updated_at +` + +type CreateOIDCConsentParams struct { + UUID string + ClientID string + Scopes string +} + +func (q *Queries) CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, createOIDCConsent, arg.UUID, arg.ClientID, arg.Scopes) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const createOIDCSession = `-- name: CreateOIDCSession :one INSERT INTO "oidc_sessions" ( "sub", @@ -80,6 +110,16 @@ func (q *Queries) DeleteExpiredOIDCSessions(ctx context.Context, arg DeleteExpir return err } +const deleteOIDCConsentByUUID = `-- name: DeleteOIDCConsentByUUID :exec +DELETE FROM "oidc_consent" +WHERE "uuid" = ? +` + +func (q *Queries) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error { + _, err := q.db.ExecContext(ctx, deleteOIDCConsentByUUID, uuid) + return err +} + const deleteOIDCSessionBySub = `-- name: DeleteOIDCSessionBySub :exec DELETE FROM "oidc_sessions" WHERE "sub" = ? @@ -90,6 +130,24 @@ func (q *Queries) DeleteOIDCSessionBySub(ctx context.Context, sub string) error return err } +const getOIDCConsentByUUID = `-- name: GetOIDCConsentByUUID :one +SELECT uuid, client_id, scopes, created_at, updated_at FROM "oidc_consent" +WHERE "uuid" = ? +` + +func (q *Queries) GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, getOIDCConsentByUUID, uuid) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const getOIDCSessionByAccessTokenHash = `-- name: GetOIDCSessionByAccessTokenHash :one SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce, userinfo_json FROM "oidc_sessions" WHERE "access_token_hash" = ? @@ -156,6 +214,32 @@ func (q *Queries) GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSess return i, err } +const updateOIDCConsent = `-- name: UpdateOIDCConsent :one +UPDATE "oidc_consent" SET + "scopes" = ?, + "updated_at" = CURRENT_TIMESTAMP +WHERE "uuid" = ? +RETURNING uuid, client_id, scopes, created_at, updated_at +` + +type UpdateOIDCConsentParams struct { + Scopes string + UUID string +} + +func (q *Queries) UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error) { + row := q.db.QueryRowContext(ctx, updateOIDCConsent, arg.Scopes, arg.UUID) + var i OidcConsent + err := row.Scan( + &i.UUID, + &i.ClientID, + &i.Scopes, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const updateOIDCSession = `-- name: UpdateOIDCSession :one UPDATE "oidc_sessions" SET "access_token_hash" = ?, diff --git a/internal/repository/sqlite/store.go b/internal/repository/sqlite/store.go index a567c871..a5ba2142 100644 --- a/internal/repository/sqlite/store.go +++ b/internal/repository/sqlite/store.go @@ -32,6 +32,14 @@ func mapErr(err error) error { return err } +func (s *Store) CreateOIDCConsent(ctx context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) { + r, err := s.q.CreateOIDCConsent(ctx, CreateOIDCConsentParams(arg)) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) CreateOIDCSession(ctx context.Context, arg repository.CreateOIDCSessionParams) (repository.OidcSession, error) { r, err := s.q.CreateOIDCSession(ctx, CreateOIDCSessionParams(arg)) if err != nil { @@ -56,6 +64,10 @@ func (s *Store) DeleteExpiredSessions(ctx context.Context, expiry int64) error { return mapErr(s.q.DeleteExpiredSessions(ctx, expiry)) } +func (s *Store) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error { + return mapErr(s.q.DeleteOIDCConsentByUUID(ctx, uuid)) +} + func (s *Store) DeleteOIDCSessionBySub(ctx context.Context, sub string) error { return mapErr(s.q.DeleteOIDCSessionBySub(ctx, sub)) } @@ -64,6 +76,14 @@ func (s *Store) DeleteSession(ctx context.Context, uuid string) error { return mapErr(s.q.DeleteSession(ctx, uuid)) } +func (s *Store) GetOIDCConsentByUUID(ctx context.Context, uuid string) (repository.OidcConsent, error) { + r, err := s.q.GetOIDCConsentByUUID(ctx, uuid) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) GetOIDCSessionByAccessTokenHash(ctx context.Context, accessTokenHash string) (repository.OidcSession, error) { r, err := s.q.GetOIDCSessionByAccessTokenHash(ctx, accessTokenHash) if err != nil { @@ -96,6 +116,14 @@ func (s *Store) GetSession(ctx context.Context, uuid string) (repository.Session return repository.Session(r), nil } +func (s *Store) UpdateOIDCConsent(ctx context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) { + r, err := s.q.UpdateOIDCConsent(ctx, UpdateOIDCConsentParams(arg)) + if err != nil { + return repository.OidcConsent{}, mapErr(err) + } + return repository.OidcConsent(r), nil +} + func (s *Store) UpdateOIDCSession(ctx context.Context, arg repository.UpdateOIDCSessionParams) (repository.OidcSession, error) { r, err := s.q.UpdateOIDCSession(ctx, UpdateOIDCSessionParams(arg)) if err != nil { diff --git a/internal/repository/store.go b/internal/repository/store.go index abd70bd3..a36f12ee 100644 --- a/internal/repository/store.go +++ b/internal/repository/store.go @@ -27,4 +27,10 @@ type Store interface { GetOIDCSessionByRefreshTokenHash(ctx context.Context, refreshTokenHash string) (OidcSession, error) GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSession, error) UpdateOIDCSession(ctx context.Context, arg UpdateOIDCSessionParams) (OidcSession, error) + + // OIDC consents + CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error) + DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error + GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error) + UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error) } diff --git a/sql/postgres/oidc_queries.sql b/sql/postgres/oidc_queries.sql index 3cd5ff99..4442ef33 100644 --- a/sql/postgres/oidc_queries.sql +++ b/sql/postgres/oidc_queries.sql @@ -46,3 +46,28 @@ UPDATE "oidc_sessions" SET "userinfo_json" = $8 WHERE "sub" = $9 RETURNING *; + +-- name: CreateOIDCConsent :one +INSERT INTO "oidc_consent" ( + "uuid", + "client_id", + "scopes" +) VALUES ( + $1, $2, $3 +) +RETURNING *; + +-- name: GetOIDCConsentByUUID :one +SELECT * FROM "oidc_consent" +WHERE "uuid" = $1; + +-- name: UpdateOIDCConsent :one +UPDATE "oidc_consent" SET + "scopes" = $1, + "updated_at" = CURRENT_TIMESTAMP +WHERE "uuid" = $2 +RETURNING *; + +-- name: DeleteOIDCConsentByUUID :exec +DELETE FROM "oidc_consent" +WHERE "uuid" = $1; diff --git a/sql/postgres/oidc_schemas.sql b/sql/postgres/oidc_schemas.sql index 2376c1d4..62265023 100644 --- a/sql/postgres/oidc_schemas.sql +++ b/sql/postgres/oidc_schemas.sql @@ -9,3 +9,11 @@ CREATE TABLE IF NOT EXISTS "oidc_sessions" ( "nonce" TEXT NOT NULL DEFAULT '', "userinfo_json" TEXT NOT NULL ); + +CREATE TABLE IF NOT EXISTS "oidc_consent" ( + "uuid" TEXT NOT NULL UNIQUE PRIMARY KEY, + "client_id" TEXT NOT NULL, + "scopes" TEXT NOT NULL, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/sql/sqlite/oidc_queries.sql b/sql/sqlite/oidc_queries.sql index 49b33cff..5ec9ea44 100644 --- a/sql/sqlite/oidc_queries.sql +++ b/sql/sqlite/oidc_queries.sql @@ -46,3 +46,28 @@ UPDATE "oidc_sessions" SET "userinfo_json" = ? WHERE "sub" = ? RETURNING *; + +-- name: CreateOIDCConsent :one +INSERT INTO "oidc_consent" ( + "uuid", + "client_id", + "scopes" +) VALUES ( + ?, ?, ? +) +RETURNING *; + +-- name: GetOIDCConsentByUUID :one +SELECT * FROM "oidc_consent" +WHERE "uuid" = ?; + +-- name: UpdateOIDCConsent :one +UPDATE "oidc_consent" SET + "scopes" = ?, + "updated_at" = CURRENT_TIMESTAMP +WHERE "uuid" = ? +RETURNING *; + +-- name: DeleteOIDCConsentByUUID :exec +DELETE FROM "oidc_consent" +WHERE "uuid" = ?; diff --git a/sql/sqlite/oidc_schemas.sql b/sql/sqlite/oidc_schemas.sql index 5a851033..e5d3a0d3 100644 --- a/sql/sqlite/oidc_schemas.sql +++ b/sql/sqlite/oidc_schemas.sql @@ -9,3 +9,11 @@ CREATE TABLE IF NOT EXISTS "oidc_sessions" ( "nonce" TEXT NOT NULL DEFAULT "", "userinfo_json" TEXT NOT NULL ); + +CREATE TABLE IF NOT EXISTS "oidc_consent" ( + "uuid" TEXT NOT NULL UNIQUE PRIMARY KEY, + "client_id" TEXT NOT NULL, + "scopes" TEXT NOT NULL, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +);