Documentation
¶
Overview ¶
Package interactiveclient — use-cases of InternalInteractiveClientService (IAM-INT-1): the lifecycle of the OAuth2 client through which a HUMAN completes an interactive sign-in ceremony.
Clean Architecture: this package defines the narrow ports below and depends on nothing but domain + the corelib operation envelope. Concrete adapters (pgx, the identity provider's admin API) live in internal/repo and internal/clients and are wired in cmd/kaname/wiring.go.
Index ¶
- Variables
- type ClientSecret
- func (s ClientSecret) Format(f fmt.State, verb rune)
- func (s ClientSecret) GoString() string
- func (s ClientSecret) IntoResponse(resp *iamv1.CreateInteractiveClientResponse)
- func (s ClientSecret) IsZero() bool
- func (s ClientSecret) LogValue() slog.Value
- func (s ClientSecret) MarshalJSON() ([]byte, error)
- func (s ClientSecret) MarshalText() ([]byte, error)
- func (s ClientSecret) String() string
- type CreateUseCase
- type DeleteUseCase
- type GetUseCase
- type Handler
- func (h *Handler) Create(ctx context.Context, req *iamv1.CreateInteractiveClientRequest) (*operationpb.Operation, error)
- func (h *Handler) Delete(ctx context.Context, req *iamv1.DeleteInteractiveClientRequest) (*operationpb.Operation, error)
- func (h *Handler) Get(ctx context.Context, req *iamv1.GetInteractiveClientRequest) (*iamv1.InteractiveClient, error)
- func (h *Handler) List(ctx context.Context, req *iamv1.ListInteractiveClientsRequest) (*iamv1.ListInteractiveClientsResponse, error)
- func (h *Handler) Update(ctx context.Context, req *iamv1.UpdateInteractiveClientRequest) (*operationpb.Operation, error)
- type ListResult
- type ListUseCase
- type ProviderClient
- type ProviderClientSpec
- type ProviderClients
- type UpdateUseCase
Constants ¶
This section is empty.
Variables ¶
var ErrClientSecretNotSerializable = errors.New("interactive client secret is not serializable")
ErrClientSecretNotSerializable — носитель не сериализуется. Отказ ГРОМКИЙ: пустое, выданное молча, было бы ложью «секрета нет».
Functions ¶
This section is empty.
Types ¶
type ClientSecret ¶ added in v0.5.0
type ClientSecret struct {
// contains filtered or unexported fields
}
ClientSecret — секрет конфиденциального клиента, выданный реестром. Нулевое значение — секрета нет (клиент со способом `none`).
func NewClientSecret ¶ added in v0.5.0
func NewClientSecret(value string) (ClientSecret, error)
NewClientSecret — секрет, отчеканенный реестром, как есть. Пустой отвергается: отсутствие секрета выражается нулевым носителем, а не пустым значением в непустом.
func (ClientSecret) Format ¶ added in v0.5.0
func (s ClientSecret) Format(f fmt.State, verb rune)
Format — заглушка на ЛЮБОМ глаголе форматирования, включая `%x` и `%q`.
func (ClientSecret) GoString ¶ added in v0.5.0
func (s ClientSecret) GoString() string
GoString — заглушка и для `%#v`.
func (ClientSecret) IntoResponse ¶ added in v0.5.0
func (s ClientSecret) IntoResponse(resp *iamv1.CreateInteractiveClientResponse)
IntoResponse — ЕДИНСТВЕННЫЙ выход значения: заполнение поля `client_secret` ответа вызова `Create`. Нулевой носитель поле не трогает.
func (ClientSecret) IsZero ¶ added in v0.5.0
func (s ClientSecret) IsZero() bool
IsZero — секрета нет.
func (ClientSecret) LogValue ¶ added in v0.5.0
func (s ClientSecret) LogValue() slog.Value
LogValue — заглушка для журнала.
func (ClientSecret) MarshalJSON ¶ added in v0.5.0
func (s ClientSecret) MarshalJSON() ([]byte, error)
MarshalJSON — отказ.
func (ClientSecret) MarshalText ¶ added in v0.5.0
func (s ClientSecret) MarshalText() ([]byte, error)
MarshalText — отказ.
func (ClientSecret) String ¶ added in v0.5.0
func (s ClientSecret) String() string
String — заглушка.
type CreateUseCase ¶
type CreateUseCase struct {
// contains filtered or unexported fields
}
CreateUseCase — registers the client at the service's own registry, then records it. The client is confidential and its secret is shown once, in the answer of this call (kaname#405).
func NewCreateUseCase ¶
func NewCreateUseCase(r clientRepo, p ProviderClients, ops operations.Repo, audiences []string, logger *slog.Logger) *CreateUseCase
NewCreateUseCase — constructor. `audiences` comes from iam's own configuration (Р2): the caller never supplies it and it is echoed output-only.
func (*CreateUseCase) Execute ¶
func (uc *CreateUseCase) Execute(ctx context.Context, req *iamv1.CreateInteractiveClientRequest) (*operationpb.Operation, error)
Execute — validate → persist Operation → register at the registry → check the secret triple → insert the row with the verification value → split the response bodies (stored without the secret, shown with it).
WHY THE PROVIDER IS CONTACTED BEFORE THE ROW IS WRITTEN, AND WHAT PAYS FOR IT. The row must carry the provider's client id, so registration comes first. If the insert then fails — most often because the name is taken — the client that was just registered is deregistered again. Without that compensation an orphan would remain at the provider that the platform has no record of and no way to name: nothing would ever remove it, and it would keep accepting ceremonies.
The Operation row is persisted BEFORE either side effect, so the id the caller receives is always pollable; a failure is recorded on it as a terminal error rather than leaving the caller polling a row that does not exist.
type DeleteUseCase ¶
type DeleteUseCase struct {
// contains filtered or unexported fields
}
DeleteUseCase — removes the client at the provider and its row.
func NewDeleteUseCase ¶
func NewDeleteUseCase(r clientRepo, p ProviderClients, ops operations.Repo, logger *slog.Logger) *DeleteUseCase
NewDeleteUseCase — constructor.
func (*DeleteUseCase) Execute ¶
func (uc *DeleteUseCase) Execute(ctx context.Context, id string) (*operationpb.Operation, error)
Execute — idempotent by construction.
Deleting an already-absent client succeeds and reports the same code as the first delete: the caller asked for the client to be gone, and it is. The row is removed first and the provider-side deregistration follows only when a row was actually taken — otherwise a repeated Delete would keep asking the provider to remove something already removed.
type GetUseCase ¶
type GetUseCase struct {
// contains filtered or unexported fields
}
GetUseCase — sync read of one client.
func (*GetUseCase) Execute ¶
func (uc *GetUseCase) Execute(ctx context.Context, id string) (domain.InteractiveClient, error)
Execute — direct-read lane: a well-formed id with no row is NOT_FOUND.
type Handler ¶
type Handler struct {
iamv1.UnimplementedInternalInteractiveClientServiceServer
// contains filtered or unexported fields
}
Handler implements iamv1.InternalInteractiveClientServiceServer.
func NewHandler ¶
func NewHandler(g *GetUseCase, l *ListUseCase, c *CreateUseCase, u *UpdateUseCase, d *DeleteUseCase) *Handler
NewHandler assembles the Handler. Composition root: cmd/kaname/wiring.go.
func (*Handler) Create ¶
func (h *Handler) Create(ctx context.Context, req *iamv1.CreateInteractiveClientRequest) (*operationpb.Operation, error)
Create — async; returns the Operation envelope.
func (*Handler) Delete ¶
func (h *Handler) Delete(ctx context.Context, req *iamv1.DeleteInteractiveClientRequest) (*operationpb.Operation, error)
Delete — async; returns the Operation envelope.
func (*Handler) Get ¶
func (h *Handler) Get(ctx context.Context, req *iamv1.GetInteractiveClientRequest) (*iamv1.InteractiveClient, error)
Get — sync read of one client.
func (*Handler) List ¶
func (h *Handler) List(ctx context.Context, req *iamv1.ListInteractiveClientsRequest) (*iamv1.ListInteractiveClientsResponse, error)
List — sync cursor-paginated read.
func (*Handler) Update ¶
func (h *Handler) Update(ctx context.Context, req *iamv1.UpdateInteractiveClientRequest) (*operationpb.Operation, error)
Update — async; returns the Operation envelope.
type ListResult ¶
type ListResult struct {
Clients []domain.InteractiveClient
NextPageToken string
}
ListResult — one page plus the cursor for the next.
type ListUseCase ¶
type ListUseCase struct {
// contains filtered or unexported fields
}
ListUseCase — sync cursor-paginated read.
func (*ListUseCase) Execute ¶
func (uc *ListUseCase) Execute(ctx context.Context, pageSize int64, pageToken, filter string) (ListResult, error)
Execute — pagination format is validated FIRST, so a garbage token or an out-of-range page size is INVALID_ARGUMENT independently of what the caller holds and of what the store contains. `page_size` out of range is REJECTED, never clamped: silent clamping returns a different page than the one asked for and the caller never learns of it.
type ProviderClient ¶
type ProviderClient struct {
ClientID string
GrantTypes []string
TokenEndpointAuthMethod string
Audiences []string
// Secret — the secret, minted by the registry, shown once in the answer of
// the call. A carrier and not a string: see client_secret.go.
Secret ClientSecret
// SecretVerifier — the verification value of Secret; it is what the row
// keeps. Zero — no secret.
SecretVerifier domain.LoginVerifier
}
ProviderClient — what the provider gives back.
The triple «method ⟺ material ⟺ secret» must agree: a secret method (`client_secret_basic`, `client_secret_post`) comes with BOTH a secret and its verification value, `none` with neither. The use-case refuses any other combination before the row is written (`secretMaterialAgrees`): the schema holds only one direction of it on purpose (a client of an external provider has no material in our row).
type ProviderClientSpec ¶
type ProviderClientSpec struct {
Name string
RedirectURIs []string
PostLogoutRedirectURIs []string
Audiences []string
// GrantTypes — decided by the use-case, forwarded verbatim. The adapter does
// not choose: this resource exists to register exactly one shape, and a
// default living in the adapter is how the other three registration paths
// ended up all meaning `client_credentials`.
GrantTypes []string
}
ProviderClientSpec — what iam asks the provider to register. The caller of the RPC supplies only the first two fields' worth of intent; everything else is iam's decision (Р2 — the audience is stamped, never accepted).
type ProviderClients ¶ added in v0.5.0
type ProviderClients interface {
Register(ctx context.Context, in ProviderClientSpec) (ProviderClient, error)
Deregister(ctx context.Context, providerClientID string) error
}
ProviderClients — порт заведения и снятия клиента интерактивного входа.
WHY IT IS A PORT AND NOT A DIRECT CALL. iam is the single facade to the provider (core rule #16); expressing the dependency here keeps the use-case testable without a live provider and keeps the provider's HTTP shape out of the business layer.
ИМЕНОВАН НАРУЖУ, и это не косметика: исполнитель собирается композиционным корнем, а не use-case, — наш собственный реестр (`*pg.OwnInteractiveClientProvider`). Прежде исполнителей было два и выбирала их посадка (kaname#313); зеркало чужого реестра снято вместе с посадкой внешнего поставщика (kaname#363), а порт остался границей: бизнес-слой не знает, чем исполняется заведение клиента.
type UpdateUseCase ¶
type UpdateUseCase struct {
// contains filtered or unexported fields
}
UpdateUseCase — applies the mutable fields.
func NewUpdateUseCase ¶
func NewUpdateUseCase(r clientRepo, ops operations.Repo, logger *slog.Logger) *UpdateUseCase
NewUpdateUseCase — constructor.
func (*UpdateUseCase) Execute ¶
func (uc *UpdateUseCase) Execute(ctx context.Context, req *iamv1.UpdateInteractiveClientRequest) (*operationpb.Operation, error)
Execute — mask discipline, then the write.