Documentation
¶
Overview ¶
Package database provides connection management for the integrated Credentials Config Service database. It supports PostgreSQL, MySQL, and SQLite backends, selected via the config.Database.Type field.
Index ¶
- Constants
- Variables
- func Close(db *sql.DB)
- func GenerateSalt() ([]byte, error)
- func InitSchema(db *sql.DB, dbType string) error
- func NewConnection(cfg config.Database) (*sql.DB, error)
- func RowToService(row ServiceRow, scopeRows []ScopeEntryRow) (config.ConfiguredService, error)
- type ClaimsQueryDB
- type CredentialDB
- type CredentialQueryDB
- type DCQLDB
- type FormatObjectDB
- type InputDescriptorDB
- type PresentationDefinitionDB
- type RefreshTokenRepository
- type RefreshTokenRow
- type ScopeEntryDB
- type ScopeEntryRow
- type ServiceRepository
- type ServiceRow
- type SqlRefreshTokenRepository
- func (r *SqlRefreshTokenRepository) ConfigureHashing(salt []byte)
- func (r *SqlRefreshTokenRepository) DeleteExpiredTokens(ctx context.Context) (int64, error)
- func (r *SqlRefreshTokenRepository) GetAndDeleteRefreshToken(ctx context.Context, token string) (*RefreshTokenRow, error)
- func (r *SqlRefreshTokenRepository) SetCleanupInterval(ctx context.Context, interval time.Duration)
- func (r *SqlRefreshTokenRepository) StoreRefreshToken(ctx context.Context, row RefreshTokenRow) error
- type SqlServiceRepository
- func (r *SqlServiceRepository) CreateService(ctx context.Context, service config.ConfiguredService) error
- func (r *SqlServiceRepository) DeleteService(ctx context.Context, id string) error
- func (r *SqlServiceRepository) GetAllServices(ctx context.Context, page, pageSize int) ([]config.ConfiguredService, int, error)
- func (r *SqlServiceRepository) GetService(ctx context.Context, id string) (config.ConfiguredService, error)
- func (r *SqlServiceRepository) GetServiceScopes(ctx context.Context, id string, oidcScope *string) ([]string, error)
- func (r *SqlServiceRepository) ServiceExists(ctx context.Context, id string) (bool, error)
- func (r *SqlServiceRepository) UpdateService(ctx context.Context, id string, service config.ConfiguredService) (config.ConfiguredService, error)
Constants ¶
const ( // DriverTypePostgres selects the PostgreSQL driver. DriverTypePostgres = "postgres" // DriverTypeMySQL selects the MySQL/MariaDB driver. DriverTypeMySQL = "mysql" // DriverTypeSQLite selects the pure-Go SQLite driver. DriverTypeSQLite = "sqlite" )
Supported database driver type constants.
Variables ¶
var ( // ErrRefreshTokenNotFound is returned when a refresh token does not // exist in the database or has already been consumed. ErrRefreshTokenNotFound = errors.New("refresh token not found") // ErrRefreshTokenInvalidIntegrity is returned when the HMAC of a stored // row does not match, indicating database-level tampering. ErrRefreshTokenInvalidIntegrity = errors.New("refresh token integrity check failed") )
var ( // ErrServiceNotFound is returned when a service ID does not exist. ErrServiceNotFound = errors.New("service not found") // ErrServiceAlreadyExists is returned on a duplicate service ID insert. ErrServiceAlreadyExists = errors.New("service already exists") )
var DB_FORMAT_MAP = map[string]string{
"dc+sd-jwt": "DC_SD_JWT",
"vc+sd-jwt": "VC_SD_JWT",
"mso_mdoc": "MSO_MDOC",
"ldp_vc": "LDP_VC",
"jwt_vc_json": "JWT_VC_JSON",
}
var FORMAT_MAP = map[string]string{
"DC_SD_JWT": "dc+sd-jwt",
"VC_SD_JWT": "vc+sd-jwt",
"MSO_MDOC": "mso_mdoc",
"LDP_VC": "ldp_vc",
"JWT_VC_JSON": "jwt_vc_json",
}
Functions ¶
func Close ¶
Close gracefully closes the database connection pool. It logs any error but does not return it, making it convenient for deferred calls.
func GenerateSalt ¶
GenerateSalt returns 32 cryptographically random bytes suitable for use with ConfigureHashing.
func InitSchema ¶
InitSchema creates the service and scope_entry tables if they do not already exist. The DDL is database-type-aware: PostgreSQL uses BIGSERIAL, SQLite uses INTEGER PRIMARY KEY AUTOINCREMENT, and MySQL uses BIGINT AUTO_INCREMENT. The function is idempotent — calling it multiple times is safe.
func NewConnection ¶
NewConnection opens a database connection pool based on the provided configuration. The returned *sql.DB is ready to use and has been verified with a ping. Callers are responsible for closing it when done.
func RowToService ¶
func RowToService(row ServiceRow, scopeRows []ScopeEntryRow) (config.ConfiguredService, error)
RowToService assembles a config.ConfiguredService from a ServiceRow and its associated ScopeEntryRow values, unmarshalling JSON text columns back into typed Go structs.
Types ¶
type ClaimsQueryDB ¶
type ClaimsQueryDB struct {
// REQUIRED if claim_sets is present in the Credential Query; OPTIONAL otherwise. A string identifying the particular claim. The value MUST be a non-empty string consisting of alphanumeric, underscore (_), or hyphen (-) characters. Within the particular claims array, the same id MUST NOT be present more than once.
Id string `json:"id,omitempty" mapstructure:"id,omitempty"`
// The value MUST be a non-empty array representing a claims path pointer that specifies the path to a claim within the Credential. See https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-claims-path-pointer
Path []interface{} `json:"path,omitempty" mapstructure:"path,omitempty"`
// A non-empty array of strings, integers or boolean values that specifies the expected values of the claim. If the values property is present, the Wallet SHOULD return the claim only if the type and value of the claim both match exactly for at least one of the elements in the array.
Values []interface{} `json:"values,omitempty" mapstructure:"values,omitempty"`
// MDoc specific parameter, ignored for all other types. The flag can be set to inform that the reader wishes to keep(store) the data. In case of false, its data is only used to be dispalyed and verified.
IntentToRetain bool `json:"intent_to_retain,omitempty" mapstructure:"intent_to_retain,omitempty"`
// MDoc specific parameter, ignored for all other types. Refers to a namespace inside an mdoc.
Namespace string `json:"namespace,omitempty" mapstructure:"namespace,omitempty"`
// MDoc specific parameter, ignored for all other types. Identifier for the data-element in the namespace.
ClaimName string `json:"claimName,omitempty" mapstructure:"claimName,omitempty"`
}
func (ClaimsQueryDB) FromVO ¶
func (cq ClaimsQueryDB) FromVO(cqVO config.ClaimsQuery) ClaimsQueryDB
func (ClaimsQueryDB) VO ¶
func (cq ClaimsQueryDB) VO() config.ClaimsQuery
type CredentialDB ¶
type CredentialDB struct {
// Type of the credential
Type string `json:"credentialType" mapstructure:"credentialType"`
// Set if the holder id should be verified
VerifyHolder bool `json:"verifyHolder" mapstructure:"verifyHolder"`
// A list of (EBSI Trusted Issuers Registry compatible) endpoints to retrieve the trusted issuers from. The attributes need to be formated to comply with the verifiers requirements.
TrustedIssuersLists []config.EndpointEntry `json:"trustedLists,omitempty" mapstructure:"trustedLists,omitempty"`
// Configuration of Holder Verfification
HolderVerification config.HolderVerification `json:"holderVerification" mapstructure:"holderVerification"`
// Does the given credential require a compliancy credential
RequireCompliance bool `json:"requireCompliance" mapstructure:"requireCompliance"`
// Configuration for the credential its inclusion into the JWT.
JwtInclusion config.JwtInclusion `json:"jwtInclusion" mapstructure:"jwtInclusion"`
// Per-credential configuration for the W3C Bitstring Status List /
// StatusList2021 revocation-list check. When omitted or disabled no
// revocation check is performed for credentials of this type, preserving
// prior behaviour for configurations that do not opt in.
CredentialStatus config.CredentialStatus `json:"credentialStatus,omitempty" mapstructure:"credentialStatus,omitempty"`
}
func (CredentialDB) FromVO ¶
func (c CredentialDB) FromVO(cv config.Credential) CredentialDB
FromVO converts a config.Credential value object into a CredentialDB, merging the separate participants and issuers lists into a unified EndpointEntry slice.
func (CredentialDB) VO ¶
func (cred CredentialDB) VO() config.Credential
VO converts a CredentialDB into its config.Credential value object, mapping the unified EndpointEntry list back into separate participants and issuers lists.
type CredentialQueryDB ¶
type CredentialQueryDB struct {
// A string identifying the Credential in the response and, if provided, the constraints in credential_sets. The value MUST be a non-empty string consisting of alphanumeric, underscore (_), or hyphen (-) characters. Within the Authorization Request, the same id MUST NOT be present more than once.
Id string `json:"id,omitempty" mapstructure:"id,omitempty"`
// A string that specifies the format of the requested Credential.
Format string `json:"format,omitempty" mapstructure:"format,omitempty"`
// A boolean which indicates whether multiple Credentials can be returned for this Credential Query. If omitted, the default value is false.
Multiple bool `json:"multiple" mapstructure:"multiple" default:"false"`
// A non-empty array of objects that specifies claims in the requested Credential. Verifiers MUST NOT point to the same claim more than once in a single query. Wallets SHOULD ignore such duplicate claim queries.
Claims []ClaimsQueryDB `json:"claims" mapstructure:"claims"`
// Defines additional properties requested by the Verifier that apply to the metadata and validity data of the Credential. The properties of this object are defined per Credential Format. If empty, no specific constraints are placed on the metadata or validity of the requested Credential.
Meta *config.MetaDataQuery `json:"meta,omitempty" mapstructure:"meta,omitempty"`
// A boolean which indicates whether the Verifier requires a Cryptographic Holder Binding proof. The default value is true, i.e., a Verifiable Presentation with Cryptographic Holder Binding is required. If set to false, the Verifier accepts a Credential without Cryptographic Holder Binding proof.
RequireCryptographicHolderBinding bool `json:"requireCryptographicHolderBinding,omitempty" mapstructure:"requireCryptographicHolderBinding,omitempty" default:"false"`
// A non-empty array containing arrays of identifiers for elements in claims that specifies which combinations of claims for the Credential are requested.
ClaimSets [][]string `json:"claimSets,omitempty" mapstructure:"claimSets,omitempty"`
// A non-empty array of objects that specifies expected authorities or trust frameworks that certify Issuers, that the Verifier will accept. Every Credential returned by the Wallet SHOULD match at least one of the conditions present in the corresponding trusted_authorities array if present.
TrustedAuthorities []config.TrustedAuthorityQuery `json:"trustedAuthorities" mapstructure:"trustedAuthorities" default:"[]"`
}
func (CredentialQueryDB) FromVO ¶
func (cq CredentialQueryDB) FromVO(cqVO config.CredentialQuery) CredentialQueryDB
func (CredentialQueryDB) VO ¶
func (cq CredentialQueryDB) VO() config.CredentialQuery
type DCQLDB ¶
type DCQLDB struct {
// A non-empty array of Credential Queries that specify the requested Credentials.
Credentials []CredentialQueryDB `json:"credentials" mapstructure:"credentials"`
// A non-empty array of Credential Set Queries that specifies additional constraints on which of the requested Credentials to return.
CredentialSets []config.CredentialSetQuery `json:"credentialSets,omitempty" mapstructure:"credentialSets,omitempty"`
}
type FormatObjectDB ¶
type FormatObjectDB struct {
// format of the key
FormatKey string `json:"formatKey" mapstructure:"formatKey"`
// list of algorithms to be requested for credential - f.e. ES256
Alg []string `json:"alg" mapstructure:"alg"`
ProofType []string `json:"proofType,omitempty" mapstructure:"proofType"`
}
func (FormatObjectDB) VO ¶
func (f FormatObjectDB) VO() config.FormatObject
type InputDescriptorDB ¶
type InputDescriptorDB struct {
// Id of the descriptor
Id string `json:"id" mapstructure:"id"`
// defines the information to be requested
Constraints config.Constraints `json:"constraints" mapstructure:"constraints"`
// Format of the credential to be requested
Format []FormatObjectDB `json:"format" mapstructure:"format"`
}
func (InputDescriptorDB) FromVO ¶
func (id InputDescriptorDB) FromVO(idVO config.InputDescriptor) InputDescriptorDB
func (InputDescriptorDB) VO ¶
func (id InputDescriptorDB) VO() config.InputDescriptor
type PresentationDefinitionDB ¶
type PresentationDefinitionDB struct {
// Id of the definition
Id string `json:"id" mapstructure:"id"`
// List of requested inputs
InputDescriptors []InputDescriptorDB `json:"inputDescriptors" mapstructure:"inputDescriptors"`
// Format of the credential to be requested
Format []FormatObjectDB `json:"format" mapstructure:"format"`
}
func (PresentationDefinitionDB) FromVO ¶
func (pd PresentationDefinitionDB) FromVO(pdVO config.PresentationDefinition) PresentationDefinitionDB
func (PresentationDefinitionDB) VO ¶
func (pd PresentationDefinitionDB) VO() config.PresentationDefinition
type RefreshTokenRepository ¶
type RefreshTokenRepository interface {
// StoreRefreshToken persists a new refresh token row.
StoreRefreshToken(ctx context.Context, row RefreshTokenRow) error
// GetAndDeleteRefreshToken atomically retrieves and deletes a refresh
// token (single-use). Returns ErrRefreshTokenNotFound if the token does
// not exist.
GetAndDeleteRefreshToken(ctx context.Context, token string) (*RefreshTokenRow, error)
// DeleteExpiredTokens removes all refresh token rows whose expires_at
// is in the past. Returns the number of rows deleted.
DeleteExpiredTokens(ctx context.Context) (int64, error)
// SetCleanupInterval starts a background goroutine that periodically calls
// DeleteExpiredTokens at the given interval. If interval is zero or
// negative, any running cleanup goroutine is cancelled and no new one is
// started. Calling again with a new interval replaces the previous one.
// The goroutine stops when ctx is cancelled.
SetCleanupInterval(ctx context.Context, interval time.Duration)
}
RefreshTokenRepository defines the data-access operations for OAuth2 refresh tokens. Implementations must be safe for concurrent use.
type RefreshTokenRow ¶
type RefreshTokenRow struct {
// Token is the primary key: the raw token when hashing is disabled, or the
// HMAC-SHA256 hex digest when hashing is enabled.
Token string
// TokenSuffix holds the last 5 characters of the original plaintext token,
// always stored regardless of hashing, for operational identification.
TokenSuffix string
// ClientID identifies the relying party that requested the token.
ClientID string
// Claims is the JSON payload extracted from the original access token JWT
// (base64url-decoded middle segment). On exchange these claims are used to
// re-issue a new access token without re-applying credential inclusion
// configurations.
Claims string
// Integrity is the HMAC-SHA256 hex digest over the raw refresh token,
// client ID, and claims. It is computed and verified by the repository;
// callers of StoreRefreshToken do not need to set this field.
Integrity string
// ExpiresAt is the Unix timestamp (seconds) at which this refresh token
// expires.
ExpiresAt int64
}
RefreshTokenRow represents a row in the refresh_token table. Each row stores the raw JWT claims so that access tokens can be re-issued without re-applying credential inclusion configurations.
type ScopeEntryDB ¶
type ScopeEntryDB struct {
// credential types with their trust configuration
Credentials []CredentialDB `json:"credentials" mapstructure:"credentials"`
// Proofs to be requested - see https://identity.foundation/presentation-exchange/#presentation-definition
PresentationDefinition *PresentationDefinitionDB `json:"presentationDefinition" mapstructure:"presentationDefinition"`
// JSON encoded query to request the credentials to be included in the presentation
DCQL *DCQLDB `json:"dcql" mapstructure:"dcql"`
// When set, the claim are flatten to plain JWT-claims before beeing included, instead of keeping the credential/presentation structure, where the claims are under the key vc or vp
FlatClaims bool `json:"flatClaims" mapstructure:"flatClaims"`
}
DATABASE models
func (ScopeEntryDB) FromVO ¶
func (se ScopeEntryDB) FromVO(seVO config.ScopeEntry) ScopeEntryDB
func (ScopeEntryDB) VO ¶
func (se ScopeEntryDB) VO() config.ScopeEntry
type ScopeEntryRow ¶
type ScopeEntryRow struct {
// ID is the auto-generated primary key.
ID int64
// ServiceID is the foreign key referencing service.id.
ServiceID string
// ScopeKey is the OIDC scope name (map key in ServiceScopes).
ScopeKey string
// Credentials is a JSON-encoded array of config.Credential objects.
Credentials string
// PresentationDefinition is a JSON-encoded config.PresentationDefinition; may be nil.
PresentationDefinition *string
// FlatClaims indicates whether claims should be flattened in the JWT.
FlatClaims bool
// DcqlQuery is a JSON-encoded config.DCQL object; may be nil.
DcqlQuery *string
}
ScopeEntryRow represents a row in the scope_entry table.
func ScopeEntryToRows ¶
func ScopeEntryToRows(serviceID string, scopes map[string]config.ScopeEntry) ([]ScopeEntryRow, error)
ScopeEntryToRows converts the ServiceScopes map from a ConfiguredService into a slice of ScopeEntryRow values, marshalling the complex fields to JSON text. An error is returned if any JSON serialisation fails.
type ServiceRepository ¶
type ServiceRepository interface {
// CreateService persists a new service together with all its scope entries.
// Returns ErrServiceAlreadyExists if a service with the same ID exists.
CreateService(ctx context.Context, service config.ConfiguredService) error
// GetService retrieves a single service by ID, including all scope entries.
// Returns ErrServiceNotFound if the ID does not exist.
GetService(ctx context.Context, id string) (config.ConfiguredService, error)
// GetAllServices returns a page of services ordered by ID and the total
// count across all pages. page is zero-based.
GetAllServices(ctx context.Context, page, pageSize int) ([]config.ConfiguredService, int, error)
// UpdateService replaces the service row and all its scope entries.
// Returns ErrServiceNotFound if the ID does not exist. Returns the
// updated service (re-read from DB) for response purposes.
UpdateService(ctx context.Context, id string, service config.ConfiguredService) (config.ConfiguredService, error)
// DeleteService removes a service and its scope entries (via CASCADE).
// Returns ErrServiceNotFound if the ID does not exist.
DeleteService(ctx context.Context, id string) error
// GetServiceScopes returns the credential types required for a scope.
// When oidcScope is nil, the service's default scope is used.
// Returns ErrServiceNotFound when the service does not exist, or
// config.ErrorNoSuchScope when the resolved scope is not configured.
GetServiceScopes(ctx context.Context, id string, oidcScope *string) ([]string, error)
// ServiceExists checks whether a service with the given ID exists.
ServiceExists(ctx context.Context, id string) (bool, error)
}
ServiceRepository defines the data-access operations for CCS services and their scope entries. Implementations must be safe for concurrent use.
type ServiceRow ¶
type ServiceRow struct {
// ID is the unique service identifier (primary key).
ID string
// DefaultOidcScope is the default OIDC scope name; may be nil.
DefaultOidcScope *string
// AuthorizationType describes the authorization mode; may be nil.
AuthorizationType *string
}
ServiceRow represents a row in the service table.
func ServiceToRow ¶
func ServiceToRow(service config.ConfiguredService) ServiceRow
ServiceToRow converts a config.ConfiguredService into a ServiceRow. The scope entries are handled separately via ScopeEntryToRows.
type SqlRefreshTokenRepository ¶
type SqlRefreshTokenRepository struct {
// contains filtered or unexported fields
}
SqlRefreshTokenRepository is a RefreshTokenRepository backed by database/sql.
func NewRefreshTokenRepository ¶
func NewRefreshTokenRepository(db *sql.DB, dbType string) *SqlRefreshTokenRepository
NewRefreshTokenRepository creates a new SqlRefreshTokenRepository for the provided database connection and driver type.
func (*SqlRefreshTokenRepository) ConfigureHashing ¶
func (r *SqlRefreshTokenRepository) ConfigureHashing(salt []byte)
ConfigureHashing enables HMAC-SHA256 hashing of tokens before storage. Must be called before any tokens are stored or retrieved. The salt must not be empty.
func (*SqlRefreshTokenRepository) DeleteExpiredTokens ¶
func (r *SqlRefreshTokenRepository) DeleteExpiredTokens(ctx context.Context) (int64, error)
DeleteExpiredTokens removes all refresh token rows whose expiration time has passed. Returns the number of rows deleted.
func (*SqlRefreshTokenRepository) GetAndDeleteRefreshToken ¶
func (r *SqlRefreshTokenRepository) GetAndDeleteRefreshToken(ctx context.Context, token string) (*RefreshTokenRow, error)
GetAndDeleteRefreshToken atomically retrieves and deletes a refresh token within a transaction, ensuring single-use semantics. Returns ErrRefreshTokenNotFound if the token does not exist, or ErrRefreshTokenInvalidIntegrity if the stored HMAC does not match (possible database-level tampering). The token is consumed regardless of the integrity outcome to prevent repeated use of a tampered row.
func (*SqlRefreshTokenRepository) SetCleanupInterval ¶
func (r *SqlRefreshTokenRepository) SetCleanupInterval(ctx context.Context, interval time.Duration)
SetCleanupInterval starts a background goroutine that periodically deletes expired refresh token rows. If interval is zero or negative, any running cleanup goroutine is cancelled and no new one is started. Calling again replaces the previous interval. The goroutine stops when ctx is cancelled.
func (*SqlRefreshTokenRepository) StoreRefreshToken ¶
func (r *SqlRefreshTokenRepository) StoreRefreshToken(ctx context.Context, row RefreshTokenRow) error
StoreRefreshToken persists a new refresh token row in the database. The token primary key is hashed when hashing is configured; the last 5 characters of the raw token are always stored in token_suffix. The integrity HMAC is computed from the configured salt and stored alongside the claims.
type SqlServiceRepository ¶
type SqlServiceRepository struct {
// contains filtered or unexported fields
}
SqlServiceRepository is a ServiceRepository backed by database/sql.
func NewServiceRepository ¶
func NewServiceRepository(db *sql.DB, dbType string) *SqlServiceRepository
NewServiceRepository creates a new SqlServiceRepository for the provided database connection and driver type. The dbType must be one of the DriverType* constants and is used to adapt SQL placeholder syntax.
func (*SqlServiceRepository) CreateService ¶
func (r *SqlServiceRepository) CreateService(ctx context.Context, service config.ConfiguredService) error
CreateService persists a new service and its scope entries within a single transaction. Returns ErrServiceAlreadyExists on duplicate ID.
func (*SqlServiceRepository) DeleteService ¶
func (r *SqlServiceRepository) DeleteService(ctx context.Context, id string) error
DeleteService removes a service. Scope entries are cascade-deleted.
func (*SqlServiceRepository) GetAllServices ¶
func (r *SqlServiceRepository) GetAllServices(ctx context.Context, page, pageSize int) ([]config.ConfiguredService, int, error)
GetAllServices returns a page of services and the total service count.
func (*SqlServiceRepository) GetService ¶
func (r *SqlServiceRepository) GetService(ctx context.Context, id string) (config.ConfiguredService, error)
GetService retrieves a single service by ID.
func (*SqlServiceRepository) GetServiceScopes ¶
func (r *SqlServiceRepository) GetServiceScopes(ctx context.Context, id string, oidcScope *string) ([]string, error)
GetServiceScopes returns the credential type names required for the given scope. When oidcScope is nil the service's default scope is used.
func (*SqlServiceRepository) ServiceExists ¶
ServiceExists returns true if a service with the given ID exists.
func (*SqlServiceRepository) UpdateService ¶
func (r *SqlServiceRepository) UpdateService(ctx context.Context, id string, service config.ConfiguredService) (config.ConfiguredService, error)
UpdateService replaces a service's data and all its scope entries.