Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile App Authentication #602

Merged
merged 28 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
838aff9
mobile auth changes and migrate v1 to v2 session
manuelsc Jul 16, 2024
8637930
use new da interface methods to user
manuelsc Jul 16, 2024
f7b6364
rework session to be more similar to how oauth handles it
manuelsc Jul 18, 2024
f7e178c
merge staging
manuelsc Jul 18, 2024
3d4b0d8
merge
manuelsc Jul 18, 2024
dd9684a
mobile auth flow
manuelsc Jul 22, 2024
174ae2d
workaround for no spec param client_name, made compatible with implic…
manuelsc Jul 23, 2024
95a154a
formatting
manuelsc Jul 23, 2024
ae62db7
add firebase token register call
manuelsc Jul 23, 2024
138b90a
remove oauth token endpoint
manuelsc Jul 24, 2024
bdaf4a9
censor email in api responses
manuelsc Jul 24, 2024
10ec9fb
censor domain part of mail as well if it is a niche domain
manuelsc Jul 24, 2024
822aa9a
Merge pull request #580 from gobitfly/BIDS-3194/api-rocketpool-tab
remoterami Jul 24, 2024
c19a790
mobile purchase endpoint
manuelsc Jul 24, 2024
b550674
merge staging
manuelsc Jul 24, 2024
45c1f61
linter
manuelsc Jul 24, 2024
31d8bb9
linter
manuelsc Jul 24, 2024
5cec14d
unparam lint
manuelsc Jul 24, 2024
58c84c9
lucca has nothing to do with this
manuelsc Jul 29, 2024
42eeee5
use csrfinsecure flag for csrf instead of debug
manuelsc Jul 30, 2024
be23b72
Merge remote-tracking branch 'origin/staging' into mobilev2
manuelsc Jul 30, 2024
651cc03
Merge branch 'staging' of github.com:gobitfly/beaconchain into mobilev2
manuelsc Jul 30, 2024
bcc9be8
include feedback
manuelsc Jul 30, 2024
1ad1692
err lint conform
manuelsc Jul 30, 2024
e228458
remove gpl header for now
manuelsc Jul 30, 2024
10bcd72
add finalized epoch to latest state
manuelsc Jul 31, 2024
d687384
merge staging
manuelsc Aug 5, 2024
5c8151e
fix for merge
manuelsc Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/go-redis/redis/v8 v8.11.5
github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7
github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
github.com/gomodule/redigo v1.9.2
Expand Down Expand Up @@ -70,6 +71,7 @@ require (
golang.org/x/text v0.14.0
golang.org/x/tools v0.18.0
google.golang.org/api v0.164.0
google.golang.org/appengine v1.6.8
google.golang.org/protobuf v1.32.0
gopkg.in/yaml.v2 v2.4.0
)
Expand Down Expand Up @@ -233,7 +235,6 @@ require (
golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
Expand Down
2 changes: 2 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
Expand Down
37 changes: 37 additions & 0 deletions backend/pkg/api/data_access/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dataaccess

import (
"context"
"database/sql"
"fmt"
"math/rand/v2"
"reflect"
Expand All @@ -10,7 +11,9 @@ import (
"github.com/go-faker/faker/v4"
"github.com/go-faker/faker/v4/pkg/options"
"github.com/gobitfly/beaconchain/pkg/api/enums"
"github.com/gobitfly/beaconchain/pkg/api/types"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/userservice"
"github.com/shopspring/decimal"
)

Expand Down Expand Up @@ -646,3 +649,37 @@ func (d *DummyService) UpdateAdConfiguration(ctx context.Context, key, jquerySel
func (d *DummyService) RemoveAdConfiguration(ctx context.Context, key string) error {
return nil
}

func (d *DummyService) GetByRefreshToken(claimUserID, claimAppID, claimDeviceID uint64, hashedRefreshToken string) (uint64, error) {
r := uint64(0)
err := commonFakeData(&r)
return r, err
}

func (d *DummyService) MigrateMobileSession(oldHashedRefreshToken, newHashedRefreshToken, deviceID, deviceName string) error {
return nil
}

func (d *DummyService) GetAppDataFromRedirectUri(callback string) (*t.OAuthAppData, error) {
r := t.OAuthAppData{}
err := commonFakeData(&r)
return &r, err
}

func (d *DummyService) AddUserDevice(userID uint64, hashedRefreshToken string, deviceID, deviceName string, appID uint64) error {
return nil
}

func (d *DummyService) AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error {
return nil
}

func (d *DummyService) GetAppSubscriptionCount(userID uint64) (uint64, error) {
r := uint64(0)
err := commonFakeData(&r)
return r, err
}

func (d *DummyService) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails types.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error {
return nil
}
97 changes: 97 additions & 0 deletions backend/pkg/api/data_access/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"math"
"time"

"github.com/gobitfly/beaconchain/pkg/api/types"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/gobitfly/beaconchain/pkg/userservice"
"github.com/pkg/errors"
)

Expand All @@ -28,6 +30,14 @@ type UserRepository interface {
GetUserInfo(ctx context.Context, id uint64) (*t.UserInfo, error)
GetUserDashboards(ctx context.Context, userId uint64) (*t.UserDashboardsData, error)
GetUserValidatorDashboardCount(ctx context.Context, userId uint64) (uint64, error)

GetByRefreshToken(claimUserID, claimAppID, claimDeviceID uint64, hashedRefreshToken string) (uint64, error)
manuelsc marked this conversation as resolved.
Show resolved Hide resolved
MigrateMobileSession(oldHashedRefreshToken, newHashedRefreshToken, deviceID, deviceName string) error
AddUserDevice(userID uint64, hashedRefreshToken string, deviceID, deviceName string, appID uint64) error
GetAppDataFromRedirectUri(callback string) (*t.OAuthAppData, error)
AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error
GetAppSubscriptionCount(userID uint64) (uint64, error)
AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails types.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error
LuccaBitfly marked this conversation as resolved.
Show resolved Hide resolved
}

func (d *DataAccessService) GetUserByEmail(ctx context.Context, email string) (uint64, error) {
Expand Down Expand Up @@ -160,6 +170,8 @@ func (d *DataAccessService) GetUserInfo(ctx context.Context, userId uint64) (*t.
return nil, fmt.Errorf("error getting userEmail: %w", err)
}

userInfo.Email = utils.CensorEmail(userInfo.Email)

err = d.userReader.SelectContext(ctx, &userInfo.ApiKeys, `SELECT api_key FROM api_keys WHERE user_id = $1`, userId)
if err != nil && err != sql.ErrNoRows {
return nil, fmt.Errorf("error getting userApiKeys: %w", err)
Expand Down Expand Up @@ -611,3 +623,88 @@ func (d *DataAccessService) GetUserValidatorDashboardCount(ctx context.Context,
`, userId)
return count, err
}

// GetByRefreshToken basically used to confirm the claimed user id with the refresh token. Returns the userId if successful
func (d *DataAccessService) GetByRefreshToken(claimUserID, claimAppID, claimDeviceID uint64, hashedRefreshToken string) (uint64, error) {
if hashedRefreshToken == "" { // sanity
return 0, errors.New("empty refresh token")
}
var userID uint64
err := d.userWriter.Get(&userID,
`SELECT user_id FROM users_devices WHERE user_id = $1 AND
refresh_token = $2 AND app_id = $3 AND id = $4 AND active = true`, claimUserID, hashedRefreshToken, claimAppID, claimDeviceID)
if errors.Is(err, sql.ErrNoRows) {
return userID, fmt.Errorf("%w: user not found via refresh token", ErrNotFound)
}
return userID, err
}

func (d *DataAccessService) MigrateMobileSession(oldHashedRefreshToken, newHashedRefreshToken, deviceID, deviceName string) error {
result, err := d.userWriter.Exec("UPDATE users_devices SET refresh_token = $2, device_identifier = $3, device_name = $4 WHERE refresh_token = $1", oldHashedRefreshToken, newHashedRefreshToken, deviceID, deviceName)
if err != nil {
return errors.Wrap(err, "Error updating refresh token")
}

rowsAffected, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "Error getting rows affected")
}

if rowsAffected != 1 {
return errors.New(fmt.Sprintf("illegal number of rows affected, expected 1 got %d", rowsAffected))
}

return err
}

func (d *DataAccessService) GetAppDataFromRedirectUri(callback string) (*t.OAuthAppData, error) {
data := t.OAuthAppData{}
err := d.userWriter.Get(&data, "SELECT id, app_name, redirect_uri, active, owner_id FROM oauth_apps WHERE active = true AND redirect_uri = $1", callback)
return &data, err
}

func (d *DataAccessService) AddUserDevice(userID uint64, hashedRefreshToken string, deviceID, deviceName string, appID uint64) error {
_, err := d.userWriter.Exec("INSERT INTO users_devices (user_id, refresh_token, device_identifier, device_name, app_id, created_ts) VALUES($1, $2, $3, $4, $5, 'NOW()') ON CONFLICT DO NOTHING",
userID, hashedRefreshToken, deviceID, deviceName, appID,
)
return err
}

func (d *DataAccessService) AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error {
_, err := d.userWriter.Exec("UPDATE users_devices SET notification_token = $1 WHERE user_id = $2 AND device_identifier = $3;",
notifyToken, userID, deviceID,
)
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("%w: user mobile device not found", ErrNotFound)
}
return err
}

func (d *DataAccessService) GetAppSubscriptionCount(userID uint64) (uint64, error) {
var count uint64
err := d.userReader.Get(&count, "SELECT COUNT(receipt) FROM users_app_subscriptions WHERE user_id = $1", userID)
return count, err
}

func (d *DataAccessService) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails types.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error {
now := time.Now()
nowTs := now.Unix()
receiptHash := utils.HashAndEncode(verifyResponse.Receipt)

query := `INSERT INTO users_app_subscriptions
(user_id, product_id, price_micros, currency, created_at, updated_at, validate_remotely, active, store, receipt, expires_at, reject_reason, receipt_hash, subscription_id)
VALUES($1, $2, $3, $4, TO_TIMESTAMP($5), TO_TIMESTAMP($6), $7, $8, $9, $10, TO_TIMESTAMP($11), $12, $13, $14)
ON CONFLICT(receipt_hash) DO UPDATE SET product_id = $2, active = $7, updated_at = TO_TIMESTAMP($5);`
var err error
if tx == nil {
_, err = d.userWriter.Exec(query,
userID, verifyResponse.ProductID, paymentDetails.PriceMicros, paymentDetails.Currency, nowTs, nowTs, verifyResponse.Valid, verifyResponse.Valid, paymentDetails.Transaction.Type, verifyResponse.Receipt, verifyResponse.ExpirationDate, verifyResponse.RejectReason, receiptHash, extSubscriptionId,
)
} else {
_, err = tx.Exec(query,
userID, verifyResponse.ProductID, paymentDetails.PriceMicros, paymentDetails.Currency, nowTs, nowTs, verifyResponse.Valid, verifyResponse.Valid, paymentDetails.Transaction.Type, verifyResponse.Receipt, verifyResponse.ExpirationDate, verifyResponse.RejectReason, receiptHash, extSubscriptionId,
)
}

return err
}
Loading
Loading