1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package ports
4
5import (
6 "context"
7 "errors"
8 "io"
9 "time"
10
11 "bnema.dev/ermite/internal/domain"
12)
13
14// The interfaces below are the use cases as the inbound adapters (the API,
15// Git over HTTP, the web) and the composition root see them. The types of package
16// usecase implement them, each assertion being made in usecase; the mocks in
17// ports/mocks stand in for them in the tests of the adapters.
18
19// AuthService turns the secret of a request into a principal. It returns
20// domain.ErrUnauthenticated for a secret that is not valid. *usecase.Auth
21// implements it.
22type AuthService interface {
23 Authenticate(ctx context.Context, secret string) (domain.Principal, error)
24}
25
26// Revalidator confirms that a principal obtained earlier is still
27// authorized. *usecase.Auth implements it.
28type Revalidator interface {
29 Revalidate(ctx context.Context, p domain.Principal) error
30}
31
32// RepoLookup is the read side of a single repository. A repository the
33// principal may not read is domain.ErrNotFound. *usecase.Repos implements it, as
34// part of RepoService.
35type RepoLookup interface {
36 Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)
37}
38
39// RepoService is the repository use cases the API serves. *usecase.Repos
40// implements it.
41type RepoService interface {
42 RepoLookup
43 ListPage(ctx context.Context, p domain.Principal, size int, token string) (RepoPage, error)
44 View(ctx context.Context, repo domain.Repo) (RepoView, error)
45 Create(ctx context.Context, p domain.Principal, params CreateRepoParams) (domain.Repo, error)
46 Update(ctx context.Context, p domain.Principal, name domain.RepoName, params UpdateRepoParams) (domain.Repo, error)
47 Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error
48}
49
50// TokenService is the access token use cases the API serves. *usecase.Tokens
51// implements it.
52type TokenService interface {
53 Create(ctx context.Context, p domain.Principal, params CreateTokenParams) (CreatedToken, error)
54 ListPage(ctx context.Context, p domain.Principal, size int, token string) (TokenPage, error)
55 Revoke(ctx context.Context, p domain.Principal, id string) error
56}
57
58// BrowseService is the read side of a repository's content. *usecase.Browse
59// implements it.
60type BrowseService interface {
61 ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListRefsParams) (RefPage, error)
62 ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)
63 GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)
64 ListPaths(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.PathList, error)
65 OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)
66 ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (HistoryPage, error)
67 GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)
68}
69
70// EventService streams the events of the forge. *usecase.Events implements it.
71type EventService interface {
72 Watch(ctx context.Context, p domain.Principal, params WatchParams, send func(WatchEvent) error) error
73}
74
75// MergeRequestService is the merge request use cases. *usecase.MergeRequests
76// implements it.
77type MergeRequestService interface {
78 Create(ctx context.Context, p domain.Principal, name domain.RepoName, params CreateMergeRequestParams) (domain.MergeRequest, error)
79 List(ctx context.Context, p domain.Principal, name domain.RepoName, params ListMergeRequestsParams) (MergeRequestPage, error)
80 Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (MergeRequestDetail, error)
81 AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params AddCommentParams) (domain.Comment, error)
82 ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64, resolved bool) (domain.Comment, error)
83 Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)
84 MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)
85 Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params MergeParams) (MergeResult, error)
86}
87
88// SigningKeyService tells which key signs the commits of the forge.
89// *usecase.SigningKey implements it.
90type SigningKeyService interface {
91 Info() domain.SigningKeyInfo
92}
93
94// SessionService is the owner's web session use cases the API serves.
95// *usecase.Sessions implements it.
96type SessionService interface {
97 CreateLoginLink(ctx context.Context, p domain.Principal) (LoginLink, error)
98 SignOutEverywhere(ctx context.Context, p domain.Principal) error
99}
100
101// ErrLinkRejected is what WebSessionService.Login returns for a link that is
102// unknown, expired or already used.
103var ErrLinkRejected = errors.New("login link rejected")
104
105// WebSessionService is the session side of the use cases, as the web needs
106// it. The composition root implements it over *usecase.Sessions.
107type WebSessionService interface {
108 // LoginLinkValid reports whether the link is pending, without using it.
109 LoginLinkValid(nonce string) bool
110 // Login uses the link and returns the session token and how long it
111 // lasts, or ErrLinkRejected.
112 Login(ctx context.Context, nonce string) (token string, lifetime time.Duration, err error)
113 // Verify checks a session token. When it is valid it returns the owner,
114 // the principal the web passes to the use cases for that request; when it
115 // is not, ok is false and err nil. An error means the check could not be
116 // made (the request then runs as anonymous).
117 Verify(ctx context.Context, token string) (owner domain.Principal, ok bool, err error)
118}
119
120// PasskeyService is the owner's passkey use cases the API serves.
121// *usecase.Passkeys implements it. Registering a passkey and signing in with
122// one are browser ceremonies and are not part of it.
123type PasskeyService interface {
124 // List returns the passkeys, oldest first.
125 List(ctx context.Context, p domain.Principal) ([]domain.Passkey, error)
126 // Remove deletes the passkey with that credential id, or returns
127 // domain.ErrNotFound, and signs every browser out.
128 Remove(ctx context.Context, p domain.Principal, id []byte) error
129}
130
131// WebPasskeyService is the passkey side of the use cases, as the web needs
132// it: the API's List and Remove, and the browser ceremonies. The composition
133// root implements it over *usecase.Passkeys. A ceremony is the opaque
134// identifier BeginLogin and BeginRegistration return; options is the JSON the
135// browser passes to the WebAuthn API.
136type WebPasskeyService interface {
137 PasskeyService
138 // BeginLogin starts a sign-in. Anonymous callers may. client is the client
139 // key of the request (clientlimit.Key of the address the failure limiter
140 // uses); it bounds how many sign-ins a client keeps waiting.
141 BeginLogin(ctx context.Context, client string) (ceremony string, options []byte, err error)
142 // FinishLogin checks the browser's response and returns the session token
143 // and how long it lasts, or ErrLinkRejected for any refusal.
144 FinishLogin(ctx context.Context, ceremony string, response []byte) (token string, lifetime time.Duration, err error)
145 // BeginRegistration starts adding a passkey. Only the owner may. name is
146 // the name the passkey will get (empty means the default): a name that
147 // breaks the rules is refused here, before any ceremony starts, and is the
148 // one FinishRegistration stores.
149 BeginRegistration(ctx context.Context, p domain.Principal, name string) (ceremony string, options []byte, err error)
150 // FinishRegistration checks the browser's response and stores the passkey
151 // under the name given to BeginRegistration.
152 FinishRegistration(ctx context.Context, p domain.Principal, ceremony string, response []byte) error
153}
154
155// PushService opens a repository for a push: it is domain.ErrUnauthenticated
156// for an anonymous caller, domain.ErrNotFound for a repository the caller may
157// not write and cannot see (or that is missing and may not be created),
158// domain.ErrForbidden for one it can see but not write. With create, a
159// missing repository the principal may write is created. *usecase.Pushes
160// implements it.
161type PushService interface {
162 Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error
163}
164
165// PushHookService is what the hook socket asks of the use cases.
166// *usecase.Pushes implements it.
167type PushHookService interface {
168 // Check decides a push before Git applies it; an error matching
169 // domain.ErrRejected refuses it, any other error refuses it as a failure.
170 Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
171 // Record handles an accepted push.
172 Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
173}
174
175// MaintenanceScheduler queues repository maintenance. *usecase.Maintainer
176// implements it.
177type MaintenanceScheduler interface {
178 Schedule(ctx context.Context, name domain.RepoName)
179}
180
181// HookTokens hands out the per-request tokens the hooks of a push present
182// back to ermite, and the socket they present them on. The Git HTTP adapter
183// implements it.
184type HookTokens interface {
185 // Register binds a new token to the repository and principal of a push.
186 // release revokes it and must be called when the request ends.
187 Register(ctx context.Context, repo domain.RepoName, p domain.Principal) (token string, release func())
188 // Socket is the path of the hook socket.
189 Socket() string
190}
191
192// FailureLimiter counts the failed authentications per client. The adapters
193// that authenticate share one, so that a client has one budget of failures.
194// *clientlimit.Limiter implements it.
195type FailureLimiter interface {
196 Blocked(ctx context.Context, addr string) (bool, time.Duration)
197 Fail(ctx context.Context, addr string)
198}