Files
zitadel/apps/api/internal/query/web_key.go
2025-08-05 15:20:32 -07:00

155 lines
4.3 KiB
Go

package query
import (
"context"
"database/sql"
_ "embed"
"encoding/json"
"errors"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
//go:embed web_key_by_state.sql
webKeyByStateQuery string
//go:embed web_key_list.sql
webKeyListQuery string
//go:embed web_key_public_keys.sql
webKeyPublicKeysQuery string
)
// GetPublicWebKeyByID gets a public key by it's keyID directly from the eventstore.
func (q *Queries) GetPublicWebKeyByID(ctx context.Context, keyID string) (webKey *jose.JSONWebKey, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
model := NewWebKeyReadModel(keyID, authz.GetInstance(ctx).InstanceID())
if err = q.eventstore.FilterToQueryReducer(ctx, model); err != nil {
return nil, err
}
if model.State == domain.WebKeyStateUnspecified || model.State == domain.WebKeyStateRemoved {
return nil, zerrors.ThrowNotFound(nil, "QUERY-AiCh0", "Errors.WebKey.NotFound")
}
return model.PublicKey, nil
}
// GetActiveSigningWebKey gets the current active signing key from the web_keys projection.
// The active signing key is eventual consistent.
func (q *Queries) GetActiveSigningWebKey(ctx context.Context) (webKey *jose.JSONWebKey, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
var keyValue *crypto.CryptoValue
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
return row.Scan(&keyValue)
},
webKeyByStateQuery,
authz.GetInstance(ctx).InstanceID(),
domain.WebKeyStateActive,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, zerrors.ThrowInternal(err, "QUERY-Opoh7", "Errors.WebKey.NoActive")
}
return nil, zerrors.ThrowInternal(err, "QUERY-Shoo0", "Errors.Internal")
}
if err = crypto.DecryptJSON(keyValue, &webKey, q.keyEncryptionAlgorithm); err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Iuk0s", "Errors.Internal")
}
return webKey, nil
}
type WebKeyDetails struct {
KeyID string
CreationDate time.Time
ChangeDate time.Time
Sequence int64
State domain.WebKeyState
Config crypto.WebKeyConfig
}
type WebKeyList struct {
Keys []WebKeyDetails
}
// ListWebKeys gets a list of [WebKeyDetails] for the complete instance from the web_keys projection.
// The list is eventual consistent.
func (q *Queries) ListWebKeys(ctx context.Context) (list []WebKeyDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
for rows.Next() {
var (
configData []byte
configType crypto.WebKeyConfigType
)
var details WebKeyDetails
if err = rows.Scan(
&details.KeyID,
&details.CreationDate,
&details.ChangeDate,
&details.Sequence,
&details.State,
&configData,
&configType,
); err != nil {
return err
}
details.Config, err = crypto.UnmarshalWebKeyConfig(configData, configType)
if err != nil {
return err
}
list = append(list, details)
}
return rows.Err()
},
webKeyListQuery,
authz.GetInstance(ctx).InstanceID(),
)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Ohl3A", "Errors.Internal")
}
return list, nil
}
// GetWebKeySet gets a JSON Web Key set from the web_keys projection.
// The set contains all existing public keys for the instance.
// The set is eventual consistent.
func (q *Queries) GetWebKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
var keys []jose.JSONWebKey
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
for rows.Next() {
var webKeyData []byte
if err = rows.Scan(&webKeyData); err != nil {
return err
}
var webKey jose.JSONWebKey
if err = json.Unmarshal(webKeyData, &webKey); err != nil {
return err
}
keys = append(keys, webKey)
}
return rows.Err()
},
webKeyPublicKeysQuery,
authz.GetInstance(ctx).InstanceID(),
)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-Eeng7", "Errors.Internal")
}
return &jose.JSONWebKeySet{Keys: keys}, nil
}