feat(users): add access token and user key endpoints
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
779d017ad9
commit
1a8fdf53ef
2 changed files with 276 additions and 0 deletions
88
users.go
88
users.go
|
|
@ -305,6 +305,36 @@ func (s *UserService) DeleteKey(ctx context.Context, id int64) error {
|
|||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListUserKeys returns all public keys for a user.
|
||||
func (s *UserService) ListUserKeys(ctx context.Context, username string, filters ...UserKeyListOptions) ([]types.PublicKey, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/keys", pathParams("username", username))
|
||||
query := make(map[string]string, len(filters))
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
query = nil
|
||||
}
|
||||
return ListAll[types.PublicKey](ctx, s.client, path, query)
|
||||
}
|
||||
|
||||
// IterUserKeys returns an iterator over all public keys for a user.
|
||||
func (s *UserService) IterUserKeys(ctx context.Context, username string, filters ...UserKeyListOptions) iter.Seq2[types.PublicKey, error] {
|
||||
path := ResolvePath("/api/v1/users/{username}/keys", pathParams("username", username))
|
||||
query := make(map[string]string, len(filters))
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
query = nil
|
||||
}
|
||||
return ListIter[types.PublicKey](ctx, s.client, path, query)
|
||||
}
|
||||
|
||||
// ListGPGKeys returns all GPG keys owned by the authenticated user.
|
||||
func (s *UserService) ListGPGKeys(ctx context.Context) ([]types.GPGKey, error) {
|
||||
return ListAll[types.GPGKey](ctx, s.client, "/api/v1/user/gpg_keys", nil)
|
||||
|
|
@ -340,6 +370,64 @@ func (s *UserService) DeleteGPGKey(ctx context.Context, id int64) error {
|
|||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListUserGPGKeys returns all GPG keys for a user.
|
||||
func (s *UserService) ListUserGPGKeys(ctx context.Context, username string) ([]types.GPGKey, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/gpg_keys", pathParams("username", username))
|
||||
return ListAll[types.GPGKey](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterUserGPGKeys returns an iterator over all GPG keys for a user.
|
||||
func (s *UserService) IterUserGPGKeys(ctx context.Context, username string) iter.Seq2[types.GPGKey, error] {
|
||||
path := ResolvePath("/api/v1/users/{username}/gpg_keys", pathParams("username", username))
|
||||
return ListIter[types.GPGKey](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetGPGKeyVerificationToken returns the token used to verify a GPG key.
|
||||
func (s *UserService) GetGPGKeyVerificationToken(ctx context.Context) (string, error) {
|
||||
data, err := s.client.GetRaw(ctx, "/api/v1/user/gpg_key_token")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// VerifyGPGKey verifies a GPG key for the authenticated user.
|
||||
func (s *UserService) VerifyGPGKey(ctx context.Context) (*types.GPGKey, error) {
|
||||
var out types.GPGKey
|
||||
if err := s.client.Post(ctx, "/api/v1/user/gpg_key_verify", nil, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListTokens returns all access tokens for a user.
|
||||
func (s *UserService) ListTokens(ctx context.Context, username string) ([]types.AccessToken, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/tokens", pathParams("username", username))
|
||||
return ListAll[types.AccessToken](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterTokens returns an iterator over all access tokens for a user.
|
||||
func (s *UserService) IterTokens(ctx context.Context, username string) iter.Seq2[types.AccessToken, error] {
|
||||
path := ResolvePath("/api/v1/users/{username}/tokens", pathParams("username", username))
|
||||
return ListIter[types.AccessToken](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateToken creates an access token for a user.
|
||||
func (s *UserService) CreateToken(ctx context.Context, username string, opts *types.CreateAccessTokenOption) (*types.AccessToken, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/tokens", pathParams("username", username))
|
||||
var out types.AccessToken
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteToken deletes an access token for a user.
|
||||
func (s *UserService) DeleteToken(ctx context.Context, username, token string) error {
|
||||
path := ResolvePath("/api/v1/users/{username}/tokens/{token}", pathParams("username", username, "token", token))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListOAuth2Applications returns all OAuth2 applications owned by the authenticated user.
|
||||
func (s *UserService) ListOAuth2Applications(ctx context.Context) ([]types.OAuth2Application, error) {
|
||||
return ListAll[types.OAuth2Application](ctx, s.client, "/api/v1/user/applications/oauth2", nil)
|
||||
|
|
|
|||
188
users_test.go
188
users_test.go
|
|
@ -1128,6 +1128,194 @@ func TestUserService_DeleteGPGKey_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUserService_GetGPGKeyVerificationToken_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/gpg_key_token" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Write([]byte("verification-token"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
token, err := f.Users.GetGPGKeyVerificationToken(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if token != "verification-token" {
|
||||
t.Errorf("got token=%q, want %q", token, "verification-token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_VerifyGPGKey_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/gpg_key_verify" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.Header.Get("Content-Type"); got != "" {
|
||||
t.Errorf("unexpected content type: %q", got)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.GPGKey{
|
||||
ID: 12,
|
||||
KeyID: "QRST7890",
|
||||
Verified: true,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
key, err := f.Users.VerifyGPGKey(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key.ID != 12 || key.KeyID != "QRST7890" || !key.Verified {
|
||||
t.Errorf("unexpected key: %+v", key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_ListTokens_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/users/alice/tokens" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.AccessToken{
|
||||
{ID: 1, Name: "ci", Scopes: []string{"repo"}},
|
||||
{ID: 2, Name: "deploy", Scopes: []string{"read:packages"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
tokens, err := f.Users.ListTokens(context.Background(), "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tokens) != 2 || tokens[0].Name != "ci" || tokens[1].Name != "deploy" {
|
||||
t.Fatalf("unexpected tokens: %+v", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_CreateToken_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/users/alice/tokens" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateAccessTokenOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Name != "ci" || len(body.Scopes) != 1 || body.Scopes[0] != "repo" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.AccessToken{
|
||||
ID: 7,
|
||||
Name: body.Name,
|
||||
Scopes: body.Scopes,
|
||||
Token: "abcdef0123456789",
|
||||
TokenLastEight: "456789",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
token, err := f.Users.CreateToken(context.Background(), "alice", &types.CreateAccessTokenOption{
|
||||
Name: "ci",
|
||||
Scopes: []string{"repo"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if token.ID != 7 || token.Name != "ci" || token.Token != "abcdef0123456789" {
|
||||
t.Fatalf("unexpected token: %+v", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_DeleteToken_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/users/alice/tokens/ci" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Users.DeleteToken(context.Background(), "alice", "ci"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_ListUserKeys_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/users/alice/keys" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("fingerprint"); got != "abc123" {
|
||||
t.Errorf("wrong fingerprint: %s", got)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.PublicKey{
|
||||
{ID: 4, Title: "laptop", Fingerprint: "abc123"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
keys, err := f.Users.ListUserKeys(context.Background(), "alice", UserKeyListOptions{Fingerprint: "abc123"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 1 || keys[0].Title != "laptop" {
|
||||
t.Fatalf("unexpected keys: %+v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_ListUserGPGKeys_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/users/alice/gpg_keys" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.GPGKey{
|
||||
{ID: 8, KeyID: "ABCD1234"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
keys, err := f.Users.ListUserGPGKeys(context.Background(), "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 1 || keys[0].KeyID != "ABCD1234" {
|
||||
t.Fatalf("unexpected gpg keys: %+v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserService_ListOAuth2Applications_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue