Skip to content

Commit

Permalink
feat(auth): email verification and unknown user error message (#439)
Browse files Browse the repository at this point in the history
* feat(auth): return error message when user is unknown

* feat(auth): add email verification check (#441)
  • Loading branch information
balzdur authored Jan 19, 2024
1 parent e5633f8 commit 0df8ab8
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 43 deletions.
10 changes: 9 additions & 1 deletion api/handle_post_firebase_id_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"net/http"
"time"

"github.com/checkmarble/marble-backend/models"
"github.com/cockroachdb/errors"
"github.com/gin-gonic/gin"
)

Expand Down Expand Up @@ -35,7 +37,13 @@ func (t *TokenHandler) GenerateToken(c *gin.Context) {
marbleToken, expirationTime, err := t.generator.GenerateToken(c.Request.Context(), key, bearerToken)
if err != nil {
_ = c.Error(fmt.Errorf("generator.GenerateToken error: %w", err))
c.Status(http.StatusUnauthorized)

var errMsg string
if errors.Is(err, models.ErrUnknownUser) {
errMsg = "ErrUnknownUser" // Change this code carefully, it is used in the frontend
}

http.Error(c.Writer, errMsg, http.StatusUnauthorized)
return
}

Expand Down
4 changes: 4 additions & 0 deletions models/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ var BadParameterError = errors.New("bad parameter")
// UnAuthorizedError is rendered with the http status code 401
var UnAuthorizedError = errors.New("unauthorized")

var (
ErrUnknownUser = errors.New("unknown user")
)

// ForbiddenError is rendered with the http status code 403
var ForbiddenError = errors.New("forbidden")

Expand Down
13 changes: 10 additions & 3 deletions repositories/firebase/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ type Client struct {

func (c *Client) verifyTokenOrCookie(ctx context.Context, firebaseToken string) (*auth.Token, error) {
token, err := c.verifier.VerifyIDToken(ctx, firebaseToken)
if err == nil {
return token, nil
if err != nil {
token, err = c.verifier.VerifySessionCookie(ctx, firebaseToken)
}
if err != nil {
return nil, err
}
return c.verifier.VerifySessionCookie(ctx, firebaseToken)
if token.Firebase.SignInProvider == "password" && token.Claims["email_verified"] == false {
return nil, fmt.Errorf("email not verified")
}

return token, nil
}

func (c *Client) VerifyFirebaseToken(ctx context.Context, firebaseToken string) (models.FirebaseIdentity, error) {
Expand Down
36 changes: 2 additions & 34 deletions repositories/firebase_token_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,10 @@ package repositories

import (
"context"
"fmt"

"github.com/checkmarble/marble-backend/models"

"firebase.google.com/go/v4/auth"
)

type FireBaseTokenRepository struct {
firebaseClient *auth.Client
}

func (repo *FireBaseTokenRepository) VerifyFirebaseToken(ctx context.Context, firebaseToken string) (models.FirebaseIdentity, error) {
token, err := repo.firebaseClient.VerifyIDToken(ctx, firebaseToken)
if err != nil {
token, err = repo.firebaseClient.VerifySessionCookie(ctx, firebaseToken)
}
if err != nil {
return models.FirebaseIdentity{}, err
}
identities := token.Firebase.Identities["email"]
if identities == nil {
return models.FirebaseIdentity{}, fmt.Errorf("unexpected firebase token content: Field email is missing")
}

emails, ok := identities.([]interface{})
if !ok || len(emails) == 0 {
return models.FirebaseIdentity{}, fmt.Errorf("unexpected firebase token content: identities is not an array")
}

email, ok := emails[0].(string)
if !ok {
return models.FirebaseIdentity{}, fmt.Errorf("unexpected firebase token content")
}

return models.FirebaseIdentity{
Email: email,
FirebaseUid: token.Subject,
}, nil
type FireBaseTokenRepository interface {
VerifyFirebaseToken(ctx context.Context, firebaseToken string) (models.FirebaseIdentity, error)
}
5 changes: 2 additions & 3 deletions repositories/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"firebase.google.com/go/v4/auth"
"github.com/Masterminds/squirrel"
"github.com/checkmarble/marble-backend/repositories/firebase"
"github.com/jackc/pgx/v5/pgxpool"
)

Expand Down Expand Up @@ -55,9 +56,7 @@ func NewRepositories(
return &Repositories{
DatabaseConnectionPoolRepository: databaseConnectionPoolRepository,
TransactionFactoryPosgresql: transactionFactory,
FirebaseTokenRepository: FireBaseTokenRepository{
firebaseClient: firebaseClient,
},
FirebaseTokenRepository: firebase.New(firebaseClient),
MarbleJwtRepository: func() MarbleJwtRepository {
if marbleJwtSigningKey == nil {
panic("Repositories does not contain a jwt signing key")
Expand Down
6 changes: 4 additions & 2 deletions usecases/token/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ func (g *Generator) fromFirebaseToken(ctx context.Context, firebaseToken string)
}

user, err = g.repository.UserByEmail(ctx, identity.Email)
if err != nil {
return "", time.Time{}, models.Credentials{}, fmt.Errorf("repository.UserByFirebaseUid error: %w", err)
if errors.Is(err, models.NotFoundError) {
return "", time.Time{}, models.Credentials{}, fmt.Errorf("%w: %w", models.ErrUnknownUser, err)
} else if err != nil {
return "", time.Time{}, models.Credentials{}, fmt.Errorf("repository.UserByEmail error: %w", err)
}

if err := g.repository.UpdateUserFirebaseUID(ctx, user.UserId, identity.FirebaseUid); err != nil {
Expand Down

0 comments on commit 0df8ab8

Please sign in to comment.