diff --git a/app.go b/app.go index 4491f02..33c1a71 100644 --- a/app.go +++ b/app.go @@ -2,14 +2,10 @@ package passage import ( "context" - "errors" "fmt" - - "github.com/lestrrat-go/jwx/v2/jwk" + "net/http" ) -const jwksUrl = "https://auth.passage.id/v1/apps/%v/.well-known/jwks.json" - type Passage = App // Config holds the configuration for the Passage SDK. @@ -24,13 +20,8 @@ type Config struct { // // Deprecated: will be renamed to `Passage` in v2. type App struct { - // Deprecated: will be removed in v2. - ID string - // Deprecated: will be removed in v2. - Config *Config - Auth *auth - User *user - client *ClientWithResponses + Auth *auth + User *user } // New creates a new Passage instance. @@ -43,23 +34,13 @@ func New(appID string, config *Config) (*Passage, error) { client, err := NewClientWithResponses( "https://api.passage.id/v1/", - withPassageVersion, + withPassageVersion(), withAPIKey(config.APIKey), ) if err != nil { return nil, err } - url := fmt.Sprintf(jwksUrl, appID) - cache := jwk.NewCache(context.Background()) - if err := cache.Register(url); err != nil { - return nil, err - } - - if _, err = cache.Refresh(context.Background(), url); err != nil { - return nil, Error{Message: "failed to fetch jwks"} - } - auth, err := newAuth(appID, client) if err != nil { return nil, err @@ -68,67 +49,23 @@ func New(appID string, config *Config) (*Passage, error) { user := newUser(appID, client) return &App{ - ID: appID, - Config: config, - client: client, - User: user, - Auth: auth, + User: user, + Auth: auth, }, nil } -// GetApp fetches the Passage app info. -// -// Deprecated: will be removed in v2. -func (a *App) GetApp() (*AppInfo, error) { - res, err := a.client.GetAppWithResponse(context.Background(), a.ID) - if err != nil { - return nil, Error{Message: "network error: failed to get Passage App Info"} - } - - if res.JSON200 != nil { - return &res.JSON200.App, nil - } - - var errorText string - var errorCode string - switch { - case res.JSON401 != nil: - errorText = res.JSON401.Error - errorCode = string(res.JSON401.Code) - case res.JSON404 != nil: - errorText = res.JSON404.Error - errorCode = string(res.JSON404.Code) - case res.JSON500 != nil: - errorText = res.JSON500.Error - errorCode = string(res.JSON500.Code) - } - - return nil, Error{ - Message: "failed to get Passage App Info", - StatusCode: res.StatusCode(), - StatusText: res.Status(), - ErrorText: errorText, - ErrorCode: errorCode, - } +func withPassageVersion() ClientOption { + return WithRequestEditorFn(func(_ context.Context, req *http.Request) error { + req.Header.Set("Passage-Version", fmt.Sprintf("passage-go %s", version)) + return nil + }) } -// CreateMagicLink creates a Magic Link for your app. -// -// Deprecated: use `Passage.Auth.CreateMagicLinkWithEmail`, `Passage.Auth.CreateMagicLinkWithPhone`, -// or `Passage.Auth.CreateMagicLinkWithUser` instead. -func (a *App) CreateMagicLink(createMagicLinkBody CreateMagicLinkBody) (*MagicLink, error) { - magicLink, err := a.Auth.createMagicLink(createMagicLinkBody, nil) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return magicLink, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } +func withAPIKey(apiKey string) ClientOption { + return WithRequestEditorFn(func(_ context.Context, req *http.Request) error { + if apiKey != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) } - } - - return magicLink, err + return nil + }) } diff --git a/app_test.go b/app_test.go index 6f94f77..3d5c4e5 100644 --- a/app_test.go +++ b/app_test.go @@ -5,41 +5,9 @@ import ( "testing" "github.com/passageidentity/passage-go" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCreateMagicLink(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - createMagicLinkBody := passage.CreateMagicLinkBody{ - Email: "chris@passage.id", - Channel: passage.EmailChannel, - TTL: 12, - Type: passage.LoginType, - } - - magicLink, err := psg.CreateMagicLink(createMagicLinkBody) - require.Nil(t, err) - assert.Equal(t, createMagicLinkBody.Email, magicLink.Identifier) - assert.Equal(t, createMagicLinkBody.TTL, magicLink.TTL) -} - -func TestGetApp(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - appInfo, err := psg.GetApp() - assert.Nil(t, err) - assert.Equal(t, PassageAppID, appInfo.ID) - -} - // should be run with the -race flag, i.e. `go test -race -run TestAppJWKSCacheWriteConcurrency` func TestAppJWKSCacheWriteConcurrency(t *testing.T) { goRoutineCount := 2 diff --git a/app_user.go b/app_user.go deleted file mode 100644 index d685c72..0000000 --- a/app_user.go +++ /dev/null @@ -1,219 +0,0 @@ -package passage - -import ( - "errors" -) - -// GetUser gets a user using their userID -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.Get` instead. -func (a *App) GetUser(userID string) (*User, error) { - user, err := a.User.Get(userID) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// GetUserByIdentifier gets a user using their identifier -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.GetByIdentifier` instead. -func (a *App) GetUserByIdentifier(identifier string) (*User, error) { - user, err := a.User.GetByIdentifier(identifier) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// ActivateUser activates a user using their userID -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.Activate` instead. -func (a *App) ActivateUser(userID string) (*User, error) { - user, err := a.User.Activate(userID) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// DeactivateUser deactivates a user using their userID -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.Deactivate` instead. -func (a *App) DeactivateUser(userID string) (*User, error) { - user, err := a.User.Deactivate(userID) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// UpdateUser receives an UpdateBody struct, updating the corresponding user's attribute(s) -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.Update` instead. -func (a *App) UpdateUser(userID string, updateBody UpdateBody) (*User, error) { - user, err := a.User.Update(userID, updateBody) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// DeleteUser receives a userID (string), and deletes the corresponding user -// returns true on success, false and error on failure (bool, err) -// -// Deprecated: Use `Passage.User.Delete` instead. -func (a *App) DeleteUser(userID string) (bool, error) { - if err := a.User.Delete(userID); err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return false, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - - return false, err - } - - return true, nil -} - -// CreateUser receives a CreateUserBody struct, creating a user with provided values -// returns user on success, error on failure -// -// Deprecated: Use `Passage.User.Create` instead. -func (a *App) CreateUser(createUserBody CreateUserBody) (*User, error) { - user, err := a.User.Create(createUserBody) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return user, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return user, err -} - -// ListUserDevices lists a user's devices -// returns a list of devices on success, error on failure -// -// Deprecated: Use `Passage.User.ListDevices` instead. -func (a *App) ListUserDevices(userID string) ([]WebAuthnDevices, error) { - devices, err := a.User.ListDevices(userID) - if err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return devices, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - } - - return devices, err -} - -// RevokeUserDevice gets a user using their userID -// returns a true success, error on failure -// -// Deprecated: Use `Passage.User.RevokeDevice` instead. -func (a *App) RevokeUserDevice(userID, deviceID string) (bool, error) { - if err := a.User.RevokeDevice(userID, deviceID); err != nil { - var passageError PassageError - if errors.As(err, &passageError) { - return false, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - - return false, err - } - - return true, nil -} - -// Signout revokes a users refresh tokens -// returns true on success, error on failure -// -// Deprecated: Use `Passage.User.RevokeRefreshTokens` instead. -func (a *App) SignOut(userID string) (bool, error) { - if err := a.User.RevokeRefreshTokens(userID); err != nil { - - var passageError PassageError - if errors.As(err, &passageError) { - return false, Error{ - ErrorText: passageError.Message, - ErrorCode: passageError.ErrorCode, - Message: passageError.Message, - StatusCode: passageError.StatusCode, - } - } - - return false, err - } - - return true, nil -} diff --git a/app_user_test.go b/app_user_test.go deleted file mode 100644 index d060727..0000000 --- a/app_user_test.go +++ /dev/null @@ -1,493 +0,0 @@ -package passage_test - -import ( - "fmt" - "strings" - "testing" - - "github.com/passageidentity/passage-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetUserInfo(t *testing.T) { - t.Run("Successful get user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - user, err := psg.GetUser(PassageUserID) - require.Nil(t, err) - assert.Equal(t, PassageUserID, user.ID) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.GetUser(PassageUserID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.GetUser("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} - -func TestGetUserInfoByIdentifier(t *testing.T) { - t.Run("Success: get user by identifer - exact email", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: RandomEmail, - } - - user, err := psg.CreateUser(createUserBody) - require.Nil(t, err) - assert.Equal(t, RandomEmail, user.Email) - - userByIdentifier, err := psg.GetUserByIdentifier(RandomEmail) - require.Nil(t, err) - - userById, err := psg.GetUser(user.ID) - require.Nil(t, err) - - assert.Equal(t, user.ID, userById.ID) - - assert.Equal(t, userById, userByIdentifier) - }) - - t.Run("Success: get user by identifer - email uppercase", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: RandomEmail, - } - - user, err := psg.CreateUser(createUserBody) - require.Nil(t, err) - assert.Equal(t, RandomEmail, user.Email) - - userByIdentifier, err := psg.GetUserByIdentifier(strings.ToUpper(RandomEmail)) - require.Nil(t, err) - - assert.Equal(t, user.ID, userByIdentifier.ID) - }) - - t.Run("Success: get user by identifer - phone number", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - phone := "+15005550007" - createUserBody := passage.CreateUserBody{ - Phone: phone, - } - - user, err := psg.CreateUser(createUserBody) - require.Nil(t, err) - assert.Equal(t, phone, user.Phone) - - userByIdentifier, err := psg.GetUserByIdentifier(phone) - require.Nil(t, err) - - userById, err := psg.GetUser(user.ID) - require.Nil(t, err) - - assert.Equal(t, user.ID, userById.ID) - - assert.Equal(t, userById, userByIdentifier) - }) - - t.Run("Error: identifier not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.GetUserByIdentifier("error@passage.id") - require.NotNil(t, err) - couldNotFindUserByIdentifierAsserts(t, err) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.GetUserByIdentifier("any@passage.id") - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) -} - -func TestActivateUser(t *testing.T) { - t.Run("Success: activate user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - user, err := psg.ActivateUser(PassageUserID) - require.Nil(t, err) - assert.Equal(t, PassageUserID, user.ID) - assert.Equal(t, passage.StatusActive, user.Status) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.ActivateUser(PassageUserID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.ActivateUser("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} -func TestDeactivateUser(t *testing.T) { - t.Run("Success: deactivate user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - user, err := psg.DeactivateUser(PassageUserID) - require.Nil(t, err) - assert.Equal(t, PassageUserID, user.ID) - assert.Equal(t, passage.StatusInactive, user.Status) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.DeactivateUser(PassageUserID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.DeactivateUser("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} - -func TestUpdateUser(t *testing.T) { - t.Run("Success: update user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - updateBody := passage.UpdateBody{ - Email: "updatedemail-gosdk@passage.id", - Phone: "+15005550012", - UserMetadata: map[string]interface{}{ - "example1": "123", - }, - } - user, err := psg.UpdateUser(PassageUserID, updateBody) - require.Nil(t, err) - assert.Equal(t, "updatedemail-gosdk@passage.id", user.Email) - assert.Equal(t, "+15005550012", user.Phone) - assert.Equal(t, "123", user.UserMetadata["example1"]) - - secondUpdateBody := passage.UpdateBody{ - Email: "updatedemail-gosdk@passage.id", - Phone: "+15005550012", - UserMetadata: map[string]interface{}{ - "example1": "456", - }, - } - user, err = psg.UpdateUser(PassageUserID, secondUpdateBody) - require.Nil(t, err) - assert.Equal(t, "updatedemail-gosdk@passage.id", user.Email) - assert.Equal(t, "+15005550012", user.Phone) - assert.Equal(t, "456", user.UserMetadata["example1"]) - }) - - t.Run("Error: Bad Request on phone number", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - updateBody := passage.UpdateBody{ - Phone: " ", - } - _, err = psg.UpdateUser(PassageUserID, updateBody) - require.NotNil(t, err) - expectedErrorText := "identifier: must be a valid E164 number." - badRequestAsserts(t, err, expectedErrorText) - }) - - t.Run("Error: Bad Request on email", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - updateBody := passage.UpdateBody{ - Email: " ", - } - _, err = psg.UpdateUser(PassageUserID, updateBody) - require.NotNil(t, err) - expectedErrorText := "identifier: must be a valid email address." - badRequestAsserts(t, err, expectedErrorText) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - updateBody := passage.UpdateBody{ - Email: "updatedemail-gosdk@passage.id", - Phone: "+15005550012", - UserMetadata: map[string]interface{}{ - "example1": "123", - }, - } - - _, err = psg.UpdateUser("PassageUserID", updateBody) - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - updateBody := passage.UpdateBody{ - Email: "updatedemail-gosdk@passage.id", - Phone: "+15005550012", - UserMetadata: map[string]interface{}{ - "example1": "123", - }, - } - - _, err = psg.UpdateUser(PassageUserID, updateBody) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) -} - -func TestCreateUser(t *testing.T) { - t.Run("Success: create user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: RandomEmail, - } - - user, err := psg.CreateUser(createUserBody) - require.Nil(t, err) - assert.Equal(t, RandomEmail, user.Email) - - CreatedUser = *user - }) - - t.Run("Success: create user with metadata", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: fmt.Sprintf("1%v", RandomEmail), - UserMetadata: map[string]interface{}{ - "example1": "test", - }, - } - - user, err := psg.CreateUser(createUserBody) - require.Nil(t, err) - assert.Equal(t, "1"+RandomEmail, user.Email) - assert.Equal(t, "test", user.UserMetadata["example1"].(string)) - - CreatedUser = *user - }) - - t.Run("Error: Bad Request - on blank phone number and email", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: "", - Phone: "", - } - _, err = psg.CreateUser(createUserBody) - - require.NotNil(t, err) - assert.Equal(t, "At least one of args.Email or args.Phone is required.", err.Error()) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - createUserBody := passage.CreateUserBody{ - Email: RandomEmail, - } - - _, err = psg.CreateUser(createUserBody) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) -} - -func TestDeleteUser(t *testing.T) { - t.Run("Success: delete user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - result, err := psg.DeleteUser(CreatedUser.ID) - require.Nil(t, err) - assert.Equal(t, result, true) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.DeleteUser(CreatedUser.ID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.DeleteUser("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} - -func TestListUserDevices(t *testing.T) { - t.Run("Success: list user devices", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - devices, err := psg.ListUserDevices(PassageUserID) - require.Nil(t, err) - assert.Equal(t, 2, len(devices)) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.ListUserDevices(PassageUserID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.ListUserDevices("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} - -// NOTE RevokeUserDevice is not tested because it is impossible to spoof webauthn to create a device to then revoke - -func TestSignOutUser(t *testing.T) { - t.Run("Success: sign out user", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, // An API_KEY environment variable is required for testing. - }) - require.Nil(t, err) - - result, err := psg.SignOut(PassageUserID) - require.Nil(t, err) - assert.Equal(t, result, true) - }) - - t.Run("Error: unauthorized", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: "PassageApiKey", - }) - require.Nil(t, err) - - _, err = psg.SignOut(PassageUserID) - require.NotNil(t, err) - unauthorizedAsserts(t, err) - }) - - t.Run("Error: not found", func(t *testing.T) { - psg, err := passage.New(PassageAppID, &passage.Config{ - APIKey: PassageApiKey, - }) - require.Nil(t, err) - - _, err = psg.SignOut("PassageUserID") - require.NotNil(t, err) - userNotFoundAsserts(t, err) - }) -} diff --git a/auth.go b/auth.go index 82f26ca..e5e2703 100644 --- a/auth.go +++ b/auth.go @@ -26,7 +26,7 @@ type auth struct { func newAuth(appID string, client *ClientWithResponses) (*auth, error) { ctx := context.Background() - url := fmt.Sprintf(jwksUrl, appID) + url := fmt.Sprintf("https://auth.passage.id/v1/apps/%v/.well-known/jwks.json", appID) cache := jwk.NewCache(ctx) if err := cache.Register(url); err != nil { return nil, err @@ -36,13 +36,11 @@ func newAuth(appID string, client *ClientWithResponses) (*auth, error) { return nil, fmt.Errorf("failed to fetch JWKS: %w", err) } - auth := auth{ + return &auth{ appID: appID, client: client, jwksCacheSet: jwk.NewCachedSet(cache, url), - } - - return &auth, nil + }, nil } // CreateMagicLink creates a Magic Link for your app using an email address. diff --git a/authentication.go b/authentication.go deleted file mode 100644 index b32d03a..0000000 --- a/authentication.go +++ /dev/null @@ -1,82 +0,0 @@ -package passage - -import ( - "net/http" - "strings" - - "github.com/golang-jwt/jwt" -) - -// AuthenticateRequest validates the JWT from either the Authorization header or cookie and returns the user ID. -// -// Deprecated: use `Passage.Auth.ValidateJWT` instead. -func (a *App) AuthenticateRequest(r *http.Request) (string, error) { - if a.Config.HeaderAuth { - return a.AuthenticateRequestWithHeader(r) - } - return a.AuthenticateRequestWithCookie(r) -} - -// AuthenticateRequestWithHeader validates the JWT from the Authorization header and returns the user ID. -// -// Deprecated: use `Passage.Auth.ValidateJWT` instead. -func (a *App) AuthenticateRequestWithHeader(r *http.Request) (string, error) { - authHeaderFields := strings.Fields(r.Header.Get("Authorization")) - if len(authHeaderFields) != 2 || authHeaderFields[0] != "Bearer" { - return "", Error{Message: "missing authentication token: expected \"Bearer\" header"} - } - - userID, valid := a.ValidateAuthToken(authHeaderFields[1]) - if !valid { - return "", Error{Message: "invalid authentication token"} - } - - return userID, nil -} - -// AuthenticateRequestWithCookie validates the JWT from the request cookie and returns the user ID. -// -// Deprecated: use `Passage.Auth.ValidateJWT` instead. -func (a *App) AuthenticateRequestWithCookie(r *http.Request) (string, error) { - authTokenCookie, err := r.Cookie("psg_auth_token") - if err != nil { - return "", Error{Message: "missing authentication token: expected \"psg_auth_token\" cookie"} - } - - userID, valid := a.ValidateAuthToken(authTokenCookie.Value) - if !valid { - return "", Error{Message: "invalid authentication token"} - } - - return userID, nil -} - -// ValidateAuthToken validates the JWT and returns the user ID. -// -// Deprecated: use `Passage.Auth.ValidateJWT` instead. -func (a *App) ValidateAuthToken(authToken string) (string, bool) { - if authToken == "" { - return "", false - } - - parsedToken, err := jwt.Parse(authToken, a.Auth.getPublicKey) - if err != nil { - return "", false - } - - // Extract claims from JWT: - claims, ok := parsedToken.Claims.(jwt.MapClaims) - if !ok { - return "", false - } - userID, ok := claims["sub"].(string) - if !ok { - return "", false - } - - if !claims.VerifyAudience(a.ID, true) { - return "", false - } - - return userID, true -} diff --git a/authentication_test.go b/authentication_test.go deleted file mode 100644 index 163815d..0000000 --- a/authentication_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package passage_test - -import ( - "fmt" - "net/http" - "testing" - - "github.com/passageidentity/passage-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAuthenticationWithCookie(t *testing.T) { - req, err := http.NewRequest("GET", "https://example.com", nil) - require.Nil(t, err) - - psg, err := passage.New(PassageAppID, &passage.Config{ - HeaderAuth: false, - }) - require.Nil(t, err) - - t.Run("fail with missing auth token", func(t *testing.T) { - userID, err := psg.AuthenticateRequest(req) - require.NotNil(t, err) - assert.Empty(t, userID) - }) - - t.Run("fail with invalid auth token", func(t *testing.T) { - req.AddCookie(&http.Cookie{ - Name: "psg_auth_token", - Value: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA", - }) - - userID, err := psg.AuthenticateRequest(req) - require.NotNil(t, err) - assert.Empty(t, userID) - }) - - t.Run("valid auth token", func(t *testing.T) { - // need to create a new request to set a different cookie - req, err := http.NewRequest("GET", "https://example.com", nil) - require.Nil(t, err) - req.AddCookie(&http.Cookie{ - Name: "psg_auth_token", - Value: PassageAuthToken, - }) - - userID, err := psg.AuthenticateRequest(req) - require.Nil(t, err) - assert.Equal(t, PassageUserID, userID) - }) -} - -func TestAuthenticationWithHeader(t *testing.T) { - req, err := http.NewRequest("GET", "https://example.com", nil) - require.Nil(t, err) - - psg, err := passage.New(PassageAppID, &passage.Config{ - HeaderAuth: true, - }) - require.Nil(t, err) - - t.Run("fail with missing auth token", func(t *testing.T) { - userID, err := psg.AuthenticateRequest(req) - require.NotNil(t, err) - assert.Empty(t, userID) - }) - - t.Run("fail with invalid auth token", func(t *testing.T) { - req.Header.Add("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA") - - userID, err := psg.AuthenticateRequest(req) - require.NotNil(t, err) - assert.Empty(t, userID) - }) - - t.Run("valid auth token", func(t *testing.T) { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", PassageAuthToken)) - - userID, err := psg.AuthenticateRequest(req) - require.Nil(t, err) - assert.Equal(t, PassageUserID, userID) - }) -} - -func TestAuthenticateToken(t *testing.T) { - psg, err := passage.New(PassageAppID, nil) - require.Nil(t, err) - - t.Run("valid auth token", func(t *testing.T) { - _, success := psg.ValidateAuthToken(PassageAuthToken) - assert.True(t, success) - }) -} diff --git a/passage_test.go b/passage_test.go index 8a9dfbe..001ca70 100644 --- a/passage_test.go +++ b/passage_test.go @@ -43,42 +43,6 @@ func TestMain(m *testing.M) { os.Exit(exitVal) } -func userNotFoundAsserts(t *testing.T, err error) { - splitError := strings.Split(err.Error(), ", ") - assert.Len(t, splitError, 4) - assert.Equal(t, "Passage Error - message: User not found", splitError[0]) - assert.Equal(t, "status_code: 404", splitError[1]) - assert.Equal(t, "error: User not found", splitError[2]) - assert.Equal(t, "error_code: user_not_found", splitError[3]) -} - -func couldNotFindUserByIdentifierAsserts(t *testing.T, err error) { - splitError := strings.Split(err.Error(), ", ") - assert.Len(t, splitError, 4) - assert.Equal(t, "Passage Error - message: Could not find user with that identifier.", splitError[0]) - assert.Equal(t, "status_code: 404", splitError[1]) - assert.Equal(t, "error: Could not find user with that identifier.", splitError[2]) - assert.Equal(t, "error_code: user_not_found", splitError[3]) -} - -func unauthorizedAsserts(t *testing.T, err error) { - splitError := strings.Split(err.Error(), ", ") - assert.Len(t, splitError, 4) - assert.Equal(t, "Passage Error - message: Invalid access token", splitError[0]) - assert.Equal(t, "status_code: 401", splitError[1]) - assert.Equal(t, "error: Invalid access token", splitError[2]) - assert.Equal(t, "error_code: invalid_access_token", splitError[3]) -} - -func badRequestAsserts(t *testing.T, err error, errorText string) { - splitError := strings.Split(err.Error(), ", ") - assert.Len(t, splitError, 4) - assert.Equal(t, "Passage Error - message: "+errorText, splitError[0]) - assert.Equal(t, "status_code: 400", splitError[1]) - assert.Equal(t, "error: "+errorText, splitError[2]) - assert.Equal(t, "error_code: invalid_request", splitError[3]) -} - func passageUserNotFoundAsserts(t *testing.T, err error) { splitError := strings.Split(err.Error(), ", ") assert.Len(t, splitError, 3) diff --git a/request.go b/request.go deleted file mode 100644 index 0781a7c..0000000 --- a/request.go +++ /dev/null @@ -1,23 +0,0 @@ -package passage - -import ( - "context" - _ "embed" - "fmt" - "net/http" -) - -var withPassageVersion ClientOption = WithRequestEditorFn( - func(ctx context.Context, req *http.Request) error { - req.Header.Set("Passage-Version", fmt.Sprintf("passage-go %s", version)) - return nil - }) - -func withAPIKey(apiKey string) ClientOption { - return WithRequestEditorFn(func(ctx context.Context, req *http.Request) error { - if apiKey != "" { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) - } - return nil - }) -}