3
0
Files
corteza/system/service/auth.go
Denis Arh 2748847ee9 Add (created|updated|delete)_by fields on record
Move data manipulation from repo to service layer
2019-01-21 14:42:07 +01:00

186 lines
4.5 KiB
Go

package service
import (
"context"
"log"
"github.com/markbates/goth"
"github.com/pkg/errors"
"github.com/crusttech/crust/system/repository"
"github.com/crusttech/crust/system/types"
)
type (
auth struct {
db db
ctx context.Context
credentials repository.CredentialsRepository
users repository.UserRepository
}
AuthService interface {
With(ctx context.Context) AuthService
Social(profile goth.User) (*types.User, error)
CheckPassword(username, password string) (*types.User, error)
ChangePassword(user *types.User, password string) error
CheckCredentials(credentialsID uint64, secret string) (*types.User, error)
RevokeCredentialsByID(user *types.User, credentialsID uint64) error
}
)
func Auth() AuthService {
return (&auth{}).With(context.Background())
}
func (svc *auth) With(ctx context.Context) AuthService {
db := repository.DB(ctx)
return &auth{
db: db,
ctx: ctx,
credentials: repository.Credentials(ctx, db),
users: repository.User(ctx, db),
}
}
// Social user verifies existance by using email value from social profile and creates user if needed
//
// It does not update user's info
func (svc *auth) Social(profile goth.User) (u *types.User, err error) {
var kind types.CredentialsKind
switch profile.Provider {
case "facebook", "gplus", "github", "linkedin":
kind = types.CredentialsKind(profile.Provider)
default:
return nil, errors.New("Unsupported provider")
}
if profile.Email == "" {
return nil, errors.New("Can not use profile data without an email")
}
return u, svc.db.Transaction(func() error {
var c *types.Credentials
if cc, err := svc.credentials.FindByCredentials(kind, profile.UserID); err == nil {
// Credentials found, load user
for _, c := range cc {
if !c.Valid() {
continue
}
if u, err = svc.users.FindByID(c.OwnerID); err != nil {
return nil
} else if u.Valid() && u.Email != profile.Email {
return errors.Errorf(
"Refusing to authenticate with non matching emails (profile: %v, db: %v) on credentials (ID: %v)",
profile.Email,
u.Email,
c.ID)
} else if u.Valid() {
// Valid user, matching emails. Bingo!
return nil
} else {
// Scenario: linked to an invalid user
u = nil
continue
}
}
// If we could not find anything useful,
// we can search for user via email
} else {
// A serious error occured, bail out...
return err
}
// Find user via his email
if u, err = svc.users.FindByEmail(profile.Email); err == repository.ErrUserNotFound {
// In case we do not have this email, create a new user
u = &types.User{
Email: profile.Email,
Name: profile.Name,
Username: profile.NickName,
Handle: profile.NickName,
}
if u, err = svc.users.Create(u); err != nil {
return err
}
c = &types.Credentials{
Kind: kind,
OwnerID: u.ID,
Credentials: profile.UserID,
}
if !profile.ExpiresAt.IsZero() {
// Copy expiration date when provided
c.ExpiresAt = &profile.ExpiresAt
}
if c, err = svc.credentials.Create(c); err != nil {
return err
}
log.Printf(
"Autheticated user (%v, %v) via %s, created user and credentials (%v)",
u.ID,
u.Email,
profile.Provider,
c.ID,
)
// Owner created
return nil
} else if err != nil {
return err
} else if !u.Valid() {
return errors.Errorf(
"Social login to an invalid/suspended user (user ID: %v)",
u.ID,
)
}
log.Printf(
"Autheticated user (%v, %v) via %s, existing user",
u.ID,
u.Email,
profile.Provider,
)
// Owner loaded, carry on.
return nil
})
}
// CheckPassword verifies username/password combination
//
// Expects plain text password as an input
func (svc *auth) CheckPassword(username, password string) (*types.User, error) {
panic("svc.auth.CheckPassword, not implemented")
}
// ChangePassword (soft) deletes old password entry and creates a new one
//
// Expects plain text password as an input
func (svc *auth) ChangePassword(user *types.User, password string) error {
panic("svc.auth.ChangePassword, not implemented")
}
// CheckCredentials searches for credentials/secret combination and returns loaded user if successful
func (svc *auth) CheckCredentials(credentialsID uint64, secret string) (*types.User, error) {
panic("svc.auth.CheckCredentials, not implemented")
}
// RevokeCredentialsByID (soft) deletes credentials by id
func (svc *auth) RevokeCredentialsByID(user *types.User, credentialsID uint64) error {
panic("svc.auth.RevokeCredentialsByID, not implemented")
}
var _ AuthService = &auth{}