Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136package oauth
import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "strings" "testing"
"github.com/bluesky-social/indigo/atproto/atcrypto" indigooauth "github.com/bluesky-social/indigo/atproto/auth/oauth" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/log" "tangled.org/core/migrator/config" "tangled.org/core/migrator/crypto" "tangled.org/core/migrator/db")
func testStore(t *testing.T) (*Store, *db.DB) { t.Helper() database, err := db.Make(context.Background(), filepath.Join(t.TempDir(), "oauth.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { database.Close() }) raw := make([]byte, 32) _, _ = rand.Read(raw) var key crypto.MasterKey copy(key[:], raw) return NewStore(database, key), database}
func TestStoreEncryptsSessionsAndReplacesTheUsersPriorGrant(t *testing.T) { store, database := testStore(t) did := syntax.DID("did:plc:alice") first := indigooauth.ClientSessionData{AccountDID: did, SessionID: "one", AccessToken: "access-secret", RefreshToken: "refresh-secret", DPoPPrivateKeyMultibase: "dpop-secret"} if err := store.SaveSession(context.Background(), first); err != nil { t.Fatal(err) } var encrypted string if err := database.QueryRow("select encrypted_data from oauth_sessions where owner_did = ?", did.String()).Scan(&encrypted); err != nil { t.Fatal(err) } for _, secret := range []string{first.AccessToken, first.RefreshToken, first.DPoPPrivateKeyMultibase} { if strings.Contains(encrypted, secret) { t.Fatalf("encrypted session contains %q", secret) } } second := first second.SessionID = "two" second.AccessToken = "new-access-secret" if err := store.SaveSession(context.Background(), second); err != nil { t.Fatal(err) } if id, err := store.SessionID(context.Background(), did.String()); err != nil || id != "two" { t.Fatalf("session id = %q, err=%v", id, err) } loaded, err := store.GetSession(context.Background(), did, "ignored") if err != nil || loaded.AccessToken != second.AccessToken { t.Fatalf("loaded=%+v err=%v", loaded, err) }}
func TestStoreEncryptsPKCEAndDPoPAuthRequestSecrets(t *testing.T) { store, database := testStore(t) request := indigooauth.AuthRequestData{State: "state", PKCEVerifier: "pkce-secret", DPoPPrivateKeyMultibase: "dpop-secret"} if err := store.SaveAuthRequestInfo(context.Background(), request); err != nil { t.Fatal(err) } var encrypted string if err := database.QueryRow("select encrypted_data from oauth_requests where state = ?", request.State).Scan(&encrypted); err != nil { t.Fatal(err) } if strings.Contains(encrypted, request.PKCEVerifier) || strings.Contains(encrypted, request.DPoPPrivateKeyMultibase) { t.Fatal("oauth request secrets were stored in plaintext") } loaded, err := store.GetAuthRequestInfo(context.Background(), request.State) if err != nil || loaded.PKCEVerifier != request.PKCEVerifier { t.Fatalf("loaded=%+v err=%v", loaded, err) } if err := store.DeleteAuthRequestInfo(context.Background(), request.State); err != nil { t.Fatal(err) } if _, err := store.GetAuthRequestInfo(context.Background(), request.State); err == nil { t.Fatal("deleted oauth request remained readable") }}
func TestClientMetadataDeclaresOnlyTheImportGrant(t *testing.T) { _, database := testStore(t) raw := make([]byte, 32) _, _ = rand.Read(raw) privateKey, err := atcrypto.GeneratePrivateKeyP256() if err != nil { t.Fatal(err) } cfg := &config.Config{ Hostname: "migrator.example.com", PrivateKey: privateKey.Multibase(), MasterKey: base64.StdEncoding.EncodeToString(raw), WorkDir: filepath.Join(t.TempDir(), "work"), } if err := cfg.Validate(); err != nil { t.Fatal(err) } client, err := NewClient(cfg, database, identity.NewMockDirectory(), log.New("oauth-test")) if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodGet, "/oauth-client-metadata.json", nil) rec := httptest.NewRecorder() client.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("metadata status = %d", rec.Code) } var metadata indigooauth.ClientMetadata if err := json.Unmarshal(rec.Body.Bytes(), &metadata); err != nil { t.Fatal(err) } if metadata.ClientID != "https://migrator.example.com/oauth-client-metadata.json" || metadata.TokenEndpointAuthMethod != "none" || !metadata.DPoPBoundAccessTokens { t.Fatalf("metadata = %+v", metadata) } if metadata.Scope != "atproto rpc:sh.tangled.repo.create?aud=* rpc:sh.tangled.repo.describeRepo?aud=* repo:sh.tangled.repo?action=create repo:sh.tangled.repo?action=update" { t.Fatalf("scope = %q", metadata.Scope) } if strings.Join(metadata.GrantTypes, " ") != "authorization_code refresh_token" || strings.Join(metadata.ResponseTypes, " ") != "code" { t.Fatalf("grant/response types = %v / %v", metadata.GrantTypes, metadata.ResponseTypes) } if len(metadata.RedirectURIs) != 1 || metadata.RedirectURIs[0] != "https://migrator.example.com/oauth/callback" { t.Fatalf("redirect uris = %v", metadata.RedirectURIs) }}