SocialAuthService.go 4.56 KB
Newer Older
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
1
2
3
package services

import (
4
5
6
7
8
9
10
11
12
13
14
15
	"fmt"
	"net/http"
	"strings"

	"backend/internal"
	"backend/kernel"
	"backend/models"
	"github.com/astaxie/beego/logs"
	h "github.com/gadelkareem/go-helpers"
	"github.com/gadelkareem/gocialite/v2"
	"github.com/gadelkareem/gocialite/v2/structs"
	"golang.org/x/oauth2"
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
16
17
18
)

type (
19
20
21
22
23
24
25
26
27
	SocialAuthService struct {
		us  *UserService
		jwt *JWTService
		s   SocialHandler
	}
	SocialHandler interface {
		New() *gocialite.Gocial
		Handle(state, code string) (*structs.User, *oauth2.Token, error)
	}
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
28
29
30
31
32
)

var providers map[string]map[string]string

func NewSocialAuthService(us *UserService, jwt *JWTService, s SocialHandler) *SocialAuthService {
33
34
	initProviderSecrets()
	return &SocialAuthService{us: us, jwt: jwt, s: s}
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
35
36
37
}

func (s *SocialAuthService) Redirect(provider string) (a *models.SocialAuth, err error) {
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	var (
		p map[string]string
		k bool
	)
	if p, k = providers[provider]; !k {
		return nil, internal.ErrInvalidSocialProvider
	}

	a = new(models.SocialAuth)
	d := s.s.New().Driver(provider)
	if p["scope"] != "" {
		d = d.Scopes([]string{p["scope"]})
	}
	a.RedirectUri, err = d.Redirect(
		p["clientID"],
		p["clientSecret"],
		fmt.Sprintf("%s/auth/social-callback/?p=%s", kernel.App.FrontEndURL, provider),
	)

	return
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
58
59
60
}

func (s *SocialAuthService) Authenticate(r *models.SocialAuth) (*models.AuthToken, error) {
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
	u, _, err := s.s.Handle(r.State, r.Code)
	if err != nil {
		logs.Debug("Social login error: %s", err)
		if strings.Contains(err.Error(), "invalid CSRF token") {
			return nil, internal.ErrInvalidCSRFToken
		}
		return nil, err
	}
	if u.Email == "" {
		return nil, internal.ErrEmailRequired
	}
	m := models.NewUser()
	m.Email = u.Email
	existingUser, err := s.us.FindUser(m, false)
	if err != nil && err != internal.ErrNotFound {
		return nil, err
	}
	if existingUser != nil {
		if existingUser.AuthenticatorEnabled {
			return nil, internal.ErrAuthenticatorCodeMissing
		}
		if existingUser.MobileVerified {
			return nil, internal.ErrMobileCodeMissing
		}
		if existingUser.SocialLogin {
			return s.jwt.SocialToken(existingUser)
		}
		return nil, internal.Errorf(http.StatusBadRequest,
			fmt.Sprintf("The email %s already has an account with us."+
				"|Please login using your login credintials or reset your password to continue.", m.Email))
	}
	m.AvatarURL = u.Avatar
	setFullName(m, u)
	setAddress(m, u)
	err = s.setUsername(m, u)
	if err != nil {
		return nil, err
	}

	err = s.us.SignUpSocial(m)
	if err != nil {
		return nil, err
	}

	go s.us.UpdateLoginAt(m)

	return s.jwt.SocialToken(m)
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
108
109
110
}

func setFullName(m *models.User, u *structs.User) {
111
112
113
114
115
116
117
118
119
120
121
122
	m.FirstName = u.FirstName
	m.LastName = u.LastName
	if m.FirstName == "" && m.LastName == "" {
		ls := strings.Split(u.FullName, " ")
		if len(ls) > 0 {
			m.FirstName = ls[0]
		}
		if len(ls) > 1 {
			m.LastName = ls[1]
		}
	}
	return
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
123
124
125
}

func setAddress(m *models.User, u *structs.User) {
126
127
128
129
130
131
132
133
134
135
	// location: Alexandra, Egypt
	if l, k := u.Raw["location"]; k {
		ls := strings.Split(l.(string), ",")
		if len(ls) > 0 {
			m.Address.City = ls[0]
		}
		if len(ls) > 1 {
			m.Country = ls[1]
		}
	}
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
136
137
138
}

func (s *SocialAuthService) setUsername(m *models.User, u *structs.User) error {
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
	if l, k := u.Raw["login"]; k {
		m.Username = l.(string)
	}
	if m.Username == "" {
		ls := strings.Split(m.Email, "@")
		if len(ls) > 0 {
			m.Username = ls[0]
		}
	}

	for {
		existingUser, err := s.us.FindUser(&models.User{Username: m.Username}, false)
		if err != nil && err != internal.ErrNotFound {
			return err
		}
		if existingUser == nil {
			return nil
		}
		m.Username = fmt.Sprintf("%s_%d", m.Username, h.RandomNumber(10000, 100000))
	}
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
159
160
161
}

func initProviderSecrets() {
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
	providers = map[string]map[string]string{
		// https://github.com/settings/developers
		"github": {
			"clientID":     kernel.App.Config.String("social::githubClientID"),
			"clientSecret": kernel.App.Config.String("social::githubClientSecret"),
			"scope":        "",
		},
		// https://developers.google.com/identity/sign-in/web/sign-in
		"google": {
			"clientID":     kernel.App.Config.String("social::googleClientID"),
			"clientSecret": kernel.App.Config.String("social::googleClientSecret"),
			"scope":        "",
		},
		// https://developers.facebook.com/apps/
		"facebook": {
			"clientID":     kernel.App.Config.String("social::facebookClientID"),
			"clientSecret": kernel.App.Config.String("social::facebookClientSecret"),
			"scope":        "",
		},
		// https://www.linkedin.com/developers/apps/
		"linkedin": {
			"clientID":     kernel.App.Config.String("social::linkedinClientID"),
			"clientSecret": kernel.App.Config.String("social::linkedinClientSecret"),
			"scope":        "",
		},
	}
Waleed Gadelkareem's avatar
Waleed Gadelkareem committed
188
}