Skip to content

Commit 0d4552d

Browse files
authored
NET-1064: Oauth User SignUp Approval Flow (#2874)
* add pending users api * insert user to pending users on first time oauth login * add pending user check on headless login * fix conflicting apis * no records error * add allowed emails domains for oauth singup to config * check if user is allowed to signup
1 parent 3152c67 commit 0d4552d

File tree

15 files changed

+361
-29
lines changed

15 files changed

+361
-29
lines changed

auth/auth.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func InitializeAuthProvider() string {
7575
if functions == nil {
7676
return ""
7777
}
78-
var _, err = fetchPassValue(logic.RandomString(64))
78+
var _, err = FetchPassValue(logic.RandomString(64))
7979
if err != nil {
8080
logger.Log(0, err.Error())
8181
return ""
@@ -156,7 +156,7 @@ func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
156156

157157
// IsOauthUser - returns
158158
func IsOauthUser(user *models.User) error {
159-
var currentValue, err = fetchPassValue("")
159+
var currentValue, err = FetchPassValue("")
160160
if err != nil {
161161
return err
162162
}
@@ -246,7 +246,7 @@ func addUser(email string) error {
246246
slog.Error("error checking for existence of admin user during OAuth login for", "email", email, "error", err)
247247
return err
248248
} // generate random password to adapt to current model
249-
var newPass, fetchErr = fetchPassValue("")
249+
var newPass, fetchErr = FetchPassValue("")
250250
if fetchErr != nil {
251251
return fetchErr
252252
}
@@ -272,7 +272,7 @@ func addUser(email string) error {
272272
return nil
273273
}
274274

275-
func fetchPassValue(newValue string) (string, error) {
275+
func FetchPassValue(newValue string) (string, error) {
276276

277277
type valueHolder struct {
278278
Value string `json:"value" bson:"value"`
@@ -334,3 +334,23 @@ func isStateCached(state string) bool {
334334
_, err := netcache.Get(state)
335335
return err == nil || strings.Contains(err.Error(), "expired")
336336
}
337+
338+
// isEmailAllowed - checks if email is allowed to signup
339+
func isEmailAllowed(email string) bool {
340+
allowedDomains := servercfg.GetAllowedEmailDomains()
341+
domains := strings.Split(allowedDomains, ",")
342+
if len(domains) == 1 && domains[0] == "*" {
343+
return true
344+
}
345+
emailParts := strings.Split(email, "@")
346+
if len(emailParts) < 2 {
347+
return false
348+
}
349+
baseDomainOfEmail := emailParts[1]
350+
for _, domain := range domains {
351+
if domain == baseDomainOfEmail {
352+
return true
353+
}
354+
}
355+
return false
356+
}

auth/azure-ad.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net/http"
99

10+
"github.com/gravitl/netmaker/database"
1011
"github.com/gravitl/netmaker/logger"
1112
"github.com/gravitl/netmaker/logic"
1213
"github.com/gravitl/netmaker/models"
@@ -60,9 +61,29 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
6061
handleOauthNotConfigured(w)
6162
return
6263
}
64+
if !isEmailAllowed(content.UserPrincipalName) {
65+
handleOauthUserNotAllowedToSignUp(w)
66+
return
67+
}
68+
// check if user approval is already pending
69+
if logic.IsPendingUser(content.UserPrincipalName) {
70+
handleOauthUserNotAllowed(w)
71+
return
72+
}
6373
_, err = logic.GetUser(content.UserPrincipalName)
64-
if err != nil { // user must not exists, so try to make one
65-
if err = addUser(content.UserPrincipalName); err != nil {
74+
if err != nil {
75+
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
76+
err = logic.InsertPendingUser(&models.User{
77+
UserName: content.UserPrincipalName,
78+
})
79+
if err != nil {
80+
handleSomethingWentWrong(w)
81+
return
82+
}
83+
handleOauthUserNotAllowed(w)
84+
return
85+
} else {
86+
handleSomethingWentWrong(w)
6687
return
6788
}
6889
}
@@ -75,7 +96,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
7596
handleOauthUserNotAllowed(w)
7697
return
7798
}
78-
var newPass, fetchErr = fetchPassValue("")
99+
var newPass, fetchErr = FetchPassValue("")
79100
if fetchErr != nil {
80101
return
81102
}

auth/error.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const oauthNotConfigured = `<!DOCTYPE html><html>
1313
const userNotAllowed = `<!DOCTYPE html><html>
1414
<body>
1515
<h3>Only Admins are allowed to access Dashboard.</h3>
16-
<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
16+
<h3>Furthermore, Admin has to approve your identity to have access to netmaker networks</h3>
17+
<p>Once your identity is approved, Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/pro/rac.html" target="_blank" rel="noopener">RemoteAccessClient.</a></p>
1718
</body>
1819
</html>
1920
`
@@ -23,6 +24,18 @@ const userNotFound = `<!DOCTYPE html><html>
2324
</body>
2425
</html>`
2526

27+
const somethingwentwrong = `<!DOCTYPE html><html>
28+
<body>
29+
<h3>Something went wrong. Contact Admin</h3>
30+
</body>
31+
</html>`
32+
33+
const notallowedtosignup = `<!DOCTYPE html><html>
34+
<body>
35+
<h3>You are not allowed to SignUp.</h3>
36+
</body>
37+
</html>`
38+
2639
func handleOauthUserNotFound(response http.ResponseWriter) {
2740
response.Header().Set("Content-Type", "text/html; charset=utf-8")
2841
response.WriteHeader(http.StatusNotFound)
@@ -35,9 +48,21 @@ func handleOauthUserNotAllowed(response http.ResponseWriter) {
3548
response.Write([]byte(userNotAllowed))
3649
}
3750

51+
func handleOauthUserNotAllowedToSignUp(response http.ResponseWriter) {
52+
response.Header().Set("Content-Type", "text/html; charset=utf-8")
53+
response.WriteHeader(http.StatusForbidden)
54+
response.Write([]byte(notallowedtosignup))
55+
}
56+
3857
// handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted
3958
func handleOauthNotConfigured(response http.ResponseWriter) {
4059
response.Header().Set("Content-Type", "text/html; charset=utf-8")
4160
response.WriteHeader(http.StatusInternalServerError)
4261
response.Write([]byte(oauthNotConfigured))
4362
}
63+
64+
func handleSomethingWentWrong(response http.ResponseWriter) {
65+
response.Header().Set("Content-Type", "text/html; charset=utf-8")
66+
response.WriteHeader(http.StatusInternalServerError)
67+
response.Write([]byte(somethingwentwrong))
68+
}

auth/github.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net/http"
99

10+
"github.com/gravitl/netmaker/database"
1011
"github.com/gravitl/netmaker/logger"
1112
"github.com/gravitl/netmaker/logic"
1213
"github.com/gravitl/netmaker/models"
@@ -60,9 +61,29 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
6061
handleOauthNotConfigured(w)
6162
return
6263
}
64+
if !isEmailAllowed(content.Login) {
65+
handleOauthUserNotAllowedToSignUp(w)
66+
return
67+
}
68+
// check if user approval is already pending
69+
if logic.IsPendingUser(content.Login) {
70+
handleOauthUserNotAllowed(w)
71+
return
72+
}
6373
_, err = logic.GetUser(content.Login)
64-
if err != nil { // user must not exist, so try to make one
65-
if err = addUser(content.Login); err != nil {
74+
if err != nil {
75+
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
76+
err = logic.InsertPendingUser(&models.User{
77+
UserName: content.Login,
78+
})
79+
if err != nil {
80+
handleSomethingWentWrong(w)
81+
return
82+
}
83+
handleOauthUserNotAllowed(w)
84+
return
85+
} else {
86+
handleSomethingWentWrong(w)
6687
return
6788
}
6889
}
@@ -75,7 +96,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
7596
handleOauthUserNotAllowed(w)
7697
return
7798
}
78-
var newPass, fetchErr = fetchPassValue("")
99+
var newPass, fetchErr = FetchPassValue("")
79100
if fetchErr != nil {
80101
return
81102
}

auth/google.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"time"
1010

11+
"github.com/gravitl/netmaker/database"
1112
"github.com/gravitl/netmaker/logger"
1213
"github.com/gravitl/netmaker/logic"
1314
"github.com/gravitl/netmaker/models"
@@ -62,9 +63,29 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
6263
handleOauthNotConfigured(w)
6364
return
6465
}
66+
if !isEmailAllowed(content.Email) {
67+
handleOauthUserNotAllowedToSignUp(w)
68+
return
69+
}
70+
// check if user approval is already pending
71+
if logic.IsPendingUser(content.Email) {
72+
handleOauthUserNotAllowed(w)
73+
return
74+
}
6575
_, err = logic.GetUser(content.Email)
66-
if err != nil { // user must not exists, so try to make one
67-
if err = addUser(content.Email); err != nil {
76+
if err != nil {
77+
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
78+
err = logic.InsertPendingUser(&models.User{
79+
UserName: content.Email,
80+
})
81+
if err != nil {
82+
handleSomethingWentWrong(w)
83+
return
84+
}
85+
handleOauthUserNotAllowed(w)
86+
return
87+
} else {
88+
handleSomethingWentWrong(w)
6889
return
6990
}
7091
}
@@ -77,7 +98,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
7798
handleOauthUserNotAllowed(w)
7899
return
79100
}
80-
var newPass, fetchErr = fetchPassValue("")
101+
var newPass, fetchErr = FetchPassValue("")
81102
if fetchErr != nil {
82103
return
83104
}

auth/headless_callback.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,24 @@ func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) {
5050
return
5151
}
5252

53-
_, err = logic.GetUser(userClaims.getUserName())
54-
if err != nil { // user must not exists, so try to make one
55-
if err = addUser(userClaims.getUserName()); err != nil {
56-
logger.Log(1, "could not create new user: ", userClaims.getUserName())
57-
return
58-
}
53+
// check if user approval is already pending
54+
if logic.IsPendingUser(userClaims.getUserName()) {
55+
handleOauthUserNotAllowed(w)
56+
return
57+
}
58+
user, err := logic.GetUser(userClaims.getUserName())
59+
if err != nil {
60+
response := returnErrTemplate("", "user not found", state, reqKeyIf)
61+
w.WriteHeader(http.StatusForbidden)
62+
w.Write(response)
63+
return
5964
}
60-
newPass, fetchErr := fetchPassValue("")
65+
newPass, fetchErr := FetchPassValue("")
6166
if fetchErr != nil {
6267
return
6368
}
6469
jwt, jwtErr := logic.VerifyAuthRequest(models.UserAuthParams{
65-
UserName: userClaims.getUserName(),
70+
UserName: user.UserName,
6671
Password: newPass,
6772
})
6873
if jwtErr != nil {

auth/oidc.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/coreos/go-oidc/v3/oidc"
10+
"github.com/gravitl/netmaker/database"
1011
"github.com/gravitl/netmaker/logger"
1112
"github.com/gravitl/netmaker/logic"
1213
"github.com/gravitl/netmaker/models"
@@ -73,9 +74,29 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
7374
handleOauthNotConfigured(w)
7475
return
7576
}
77+
if !isEmailAllowed(content.Email) {
78+
handleOauthUserNotAllowedToSignUp(w)
79+
return
80+
}
81+
// check if user approval is already pending
82+
if logic.IsPendingUser(content.Email) {
83+
handleOauthUserNotAllowed(w)
84+
return
85+
}
7686
_, err = logic.GetUser(content.Email)
77-
if err != nil { // user must not exists, so try to make one
78-
if err = addUser(content.Email); err != nil {
87+
if err != nil {
88+
if database.IsEmptyRecord(err) { // user must not exist, so try to make one
89+
err = logic.InsertPendingUser(&models.User{
90+
UserName: content.Email,
91+
})
92+
if err != nil {
93+
handleSomethingWentWrong(w)
94+
return
95+
}
96+
handleOauthUserNotAllowed(w)
97+
return
98+
} else {
99+
handleSomethingWentWrong(w)
79100
return
80101
}
81102
}
@@ -88,7 +109,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
88109
handleOauthUserNotAllowed(w)
89110
return
90111
}
91-
var newPass, fetchErr = fetchPassValue("")
112+
var newPass, fetchErr = FetchPassValue("")
92113
if fetchErr != nil {
93114
return
94115
}

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ type ServerConfig struct {
9292
JwtValidityDuration time.Duration `yaml:"jwt_validity_duration"`
9393
RacAutoDisable bool `yaml:"rac_auto_disable"`
9494
CacheEnabled string `yaml:"caching_enabled"`
95+
AllowedEmailDomains string `yaml:"allowed_email_domains"`
9596
}
9697

9798
// SQLConfig - Generic SQL Config

0 commit comments

Comments
 (0)