Skip to content

Commit

Permalink
feat: Psg 5154 introduce new method names (#98)
Browse files Browse the repository at this point in the history
* user methods added

* deprecate getApp

* revert go version to 1.16

* feat: add method validate jwt

* docs: deprecate ValidateAuthToken

* docs: deprecation notice on apps

* fix: error check

* style: typo

* test error check

* feat: private app user

* test: adjust app user

* chore: remove unused err

* docs: update change log

* builds: mod tidy

* revert: change log + version

* docs: update deprecated comments

* feat: appUser private

* feat: app user constructor

* feat: ValidateJWT

* style: format

* docs: move where deprecated text is in doc string
  • Loading branch information
tdeshong authored Nov 20, 2024
1 parent ca17494 commit c63c75e
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 6 deletions.
10 changes: 9 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ type Config struct {
HeaderAuth bool
}

// Deprecated: will be renamed to `Passage` in v2
type App struct {
ID string
ID string
// Deprecated
Config *Config
User *appUser
client *ClientWithResponses
jwksCacheSet jwk.Set
}

// Deprecated: Will be replaced with a different signature in v2
func New(appID string, config *Config) (*App, error) {
if config == nil {
config = &Config{}
Expand Down Expand Up @@ -53,11 +57,15 @@ func New(appID string, config *Config) (*App, error) {

app.jwksCacheSet = jwk.NewCachedSet(cache, url)

app.User = newAppUser(app)

return &app, nil
}

// GetApp gets information about an app
// returns App on success, error on failure
//
// Deprecated: GetApp - this method will not be replaced
func (a *App) GetApp() (*AppInfo, error) {
res, err := a.client.GetAppWithResponse(context.Background(), a.ID)
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletions app_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package passage

type PassageUser = User
type appUser struct {
app App
}

const (
UserIDDoesNotExist string = "passage User with ID \"%v\" does not exist"
IdentifierDoesNotExist string = "passage User with Identifier \"%v\" does not exist"
)

func newAppUser(app App) *appUser {
appUser := appUser{
app: app,
}

return &appUser
}

// Get gets a user using their userID
// returns user on success, error on failure
func (a *appUser) Get(userID string) (*PassageUser, error) {
return a.app.GetUser(userID)
}

// GetByIdentifier gets a user using their identifier
// returns user on success, error on failure
func (a *appUser) GetByIdentifier(identifier string) (*PassageUser, error) {
return a.app.GetUserByIdentifier(identifier)
}

// Activate activates a user using their userID
// returns user on success, error on failure
func (a *appUser) Activate(userID string) (*PassageUser, error) {
return a.app.ActivateUser(userID)
}

// Deactivate deactivates a user using their userID
// returns user on success, error on failure
func (a *appUser) Deactivate(userID string) (*PassageUser, error) {
return a.app.DeactivateUser(userID)
}

// Update receives an UpdateBody struct, updating the corresponding user's attribute(s)
// returns user on success, error on failure
func (a *appUser) Update(userID string, updateBody UpdateBody) (*PassageUser, error) {
return a.app.UpdateUser(userID, updateBody)
}

// Delete deletes a user by their user string
// returns true on success, false and error on failure (bool, err)
func (a *appUser) Delete(userID string) (bool, error) {
return a.app.DeleteUser(userID)
}

// Create receives a CreateUserBody struct, creating a user with provided values
// returns user on success, error on failure
func (a *appUser) Create(createUserBody CreateUserBody) (*PassageUser, error) {
return a.app.CreateUser(createUserBody)
}

// ListDevices lists a user's devices
// returns a list of devices on success, error on failure
func (a *appUser) ListDevices(userID string) ([]WebAuthnDevices, error) {
return a.app.ListUserDevices(userID)
}

// RevokeDevice gets a user using their userID
// returns a true success, error on failure
func (a *appUser) RevokeDevice(userID, deviceID string) (bool, error) {
return a.app.RevokeUserDevice(userID, deviceID)
}

// Signout revokes a users refresh tokens
// returns true on success, error on failure
func (a *appUser) SignOut(userID string) (bool, error) {
return a.app.SignOut(userID)
}
235 changes: 235 additions & 0 deletions app_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package passage_test

import (
"fmt"
"strings"
"testing"

"github.com/passageidentity/passage-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetInfo(t *testing.T) {
psg, err := passage.New(PassageAppID, &passage.Config{
APIKey: PassageApiKey,
})
require.Nil(t, err)

user, err := psg.User.Get(PassageUserID)
require.Nil(t, err)
assert.Equal(t, PassageUserID, user.ID)
}

func TestGetInfoByIdentifier(t *testing.T) {
psg, err := passage.New(PassageAppID, &passage.Config{
APIKey: PassageApiKey,
})
require.Nil(t, err)

createUserBody := passage.CreateUserBody{
Email: RandomEmail,
}

user, err := psg.User.Create(createUserBody)
require.Nil(t, err)
assert.Equal(t, RandomEmail, user.Email)

userByIdentifier, err := psg.User.GetByIdentifier(RandomEmail)
require.Nil(t, err)

userById, err := psg.User.Get(user.ID)
require.Nil(t, err)

assert.Equal(t, user.ID, userById.ID)

assert.Equal(t, userById, userByIdentifier)
}

func TestGetInfoByIdentifierEmailUpperCase(t *testing.T) {
psg, err := passage.New(PassageAppID, &passage.Config{
APIKey: PassageApiKey,
})
require.Nil(t, err)

createUserBody := passage.CreateUserBody{
Email: RandomEmail,
}

user, err := psg.User.Create(createUserBody)
require.Nil(t, err)
assert.Equal(t, RandomEmail, user.Email)

userByIdentifier, err := psg.User.GetByIdentifier(strings.ToUpper(RandomEmail))
require.Nil(t, err)

assert.Equal(t, user.ID, userByIdentifier.ID)
}

func TestGetInfoByIdentifierPhone(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.User.Create(createUserBody)
require.Nil(t, err)
assert.Equal(t, phone, user.Phone)

userByIdentifier, err := psg.User.GetByIdentifier(phone)
require.Nil(t, err)

userById, err := psg.User.Get(user.ID)
require.Nil(t, err)

assert.Equal(t, user.ID, userById.ID)

assert.Equal(t, userById, userByIdentifier)
}

func TestGetInfoByIdentifierError(t *testing.T) {
psg, err := passage.New(PassageAppID, &passage.Config{
APIKey: PassageApiKey,
})
require.Nil(t, err)

_, err = psg.User.GetByIdentifier("[email protected]")
require.NotNil(t, err)

expectedMessage := "passage User with Identifier \"[email protected]\" does not exist"
assert.Contains(t, err.Error(), expectedMessage)
}

func TestActivate(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.User.Activate(PassageUserID)
require.Nil(t, err)
assert.Equal(t, PassageUserID, user.ID)
assert.Equal(t, passage.StatusActive, user.Status)
}
func TestDeactivate(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.User.Deactivate(PassageUserID)
require.Nil(t, err)
assert.Equal(t, PassageUserID, user.ID)
assert.Equal(t, passage.StatusInactive, user.Status)
}

func TestUpdate(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: "[email protected]",
Phone: "+15005550012",
UserMetadata: map[string]interface{}{
"example1": "123",
},
}
user, err := psg.User.Update(PassageUserID, updateBody)
require.Nil(t, err)
assert.Equal(t, "[email protected]", user.Email)
assert.Equal(t, "+15005550012", user.Phone)
assert.Equal(t, "123", user.UserMetadata["example1"])

secondUpdateBody := passage.UpdateBody{
Email: "[email protected]",
Phone: "+15005550012",
UserMetadata: map[string]interface{}{
"example1": "456",
},
}
user, err = psg.User.Update(PassageUserID, secondUpdateBody)
require.Nil(t, err)
assert.Equal(t, "[email protected]", user.Email)
assert.Equal(t, "+15005550012", user.Phone)
assert.Equal(t, "456", user.UserMetadata["example1"])
}

func TestCreate(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.User.Create(createUserBody)
require.Nil(t, err)
assert.Equal(t, RandomEmail, user.Email)

CreatedUser = *user
}

func TestCreateWithMetadata(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.User.Create(createUserBody)
require.Nil(t, err)
assert.Equal(t, "1"+RandomEmail, user.Email)
assert.Equal(t, "test", user.UserMetadata["example1"].(string))

CreatedUser = *user
}

func TestDelete(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.User.Delete(CreatedUser.ID)
require.Nil(t, err)
assert.Equal(t, result, true)
}

func TestListDevices(t *testing.T) {
psg, err := passage.New(PassageAppID, &passage.Config{
APIKey: PassageApiKey,
})
require.Nil(t, err)

devices, err := psg.User.ListDevices(PassageUserID)
require.Nil(t, err)
assert.Equal(t, 2, len(devices))
}

// NOTE RevokeUserDevice is not tested because it is impossible to spoof webauthn to create a device to then revoke

func TestSignOut(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.User.SignOut(PassageUserID)
require.Nil(t, err)
assert.Equal(t, result, true)
}
8 changes: 8 additions & 0 deletions authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func (a *App) AuthenticateRequestWithCookie(r *http.Request) (string, error) {

// ValidateAuthToken determines whether a JWT is valid or not
// returns userID (string) on success, error on failure
//
// Deprecated: Use ValidateJWT() instead.
func (a *App) ValidateAuthToken(authToken string) (string, bool) {
parsedToken, err := jwt.Parse(authToken, a.getPublicKey)
if err != nil {
Expand All @@ -87,3 +89,9 @@ func (a *App) ValidateAuthToken(authToken string) (string, bool) {

return userID, true
}

// ValidateJWT determines whether a JWT is valid or not
// returns userID (string) on success, error on failure
func (a *App) ValidateJWT(authToken string) (string, bool) {
return a.ValidateAuthToken(authToken)
}
Loading

0 comments on commit c63c75e

Please sign in to comment.