code.gitea.io/gitea@v1.21.7/services/auth/source/smtp/source_authenticate.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package smtp
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"net/smtp"
    10  	"net/textproto"
    11  	"strings"
    12  
    13  	auth_model "code.gitea.io/gitea/models/auth"
    14  	user_model "code.gitea.io/gitea/models/user"
    15  	"code.gitea.io/gitea/modules/util"
    16  )
    17  
    18  // Authenticate queries if the provided login/password is authenticates against the SMTP server
    19  // Users will be autoregistered as required
    20  func (source *Source) Authenticate(ctx context.Context, user *user_model.User, userName, password string) (*user_model.User, error) {
    21  	// Verify allowed domains.
    22  	if len(source.AllowedDomains) > 0 {
    23  		idx := strings.Index(userName, "@")
    24  		if idx == -1 {
    25  			return nil, user_model.ErrUserNotExist{Name: userName}
    26  		} else if !util.SliceContainsString(strings.Split(source.AllowedDomains, ","), userName[idx+1:], true) {
    27  			return nil, user_model.ErrUserNotExist{Name: userName}
    28  		}
    29  	}
    30  
    31  	var auth smtp.Auth
    32  	switch source.Auth {
    33  	case PlainAuthentication:
    34  		auth = smtp.PlainAuth("", userName, password, source.Host)
    35  	case LoginAuthentication:
    36  		auth = &loginAuthenticator{userName, password}
    37  	case CRAMMD5Authentication:
    38  		auth = smtp.CRAMMD5Auth(userName, password)
    39  	default:
    40  		return nil, errors.New("unsupported SMTP auth type")
    41  	}
    42  
    43  	if err := Authenticate(auth, source); err != nil {
    44  		// Check standard error format first,
    45  		// then fallback to worse case.
    46  		tperr, ok := err.(*textproto.Error)
    47  		if (ok && tperr.Code == 535) ||
    48  			strings.Contains(err.Error(), "Username and Password not accepted") {
    49  			return nil, user_model.ErrUserNotExist{Name: userName}
    50  		}
    51  		if (ok && tperr.Code == 534) ||
    52  			strings.Contains(err.Error(), "Application-specific password required") {
    53  			return nil, user_model.ErrUserNotExist{Name: userName}
    54  		}
    55  		return nil, err
    56  	}
    57  
    58  	if user != nil {
    59  		return user, nil
    60  	}
    61  
    62  	username := userName
    63  	idx := strings.Index(userName, "@")
    64  	if idx > -1 {
    65  		username = userName[:idx]
    66  	}
    67  
    68  	user = &user_model.User{
    69  		LowerName:   strings.ToLower(username),
    70  		Name:        strings.ToLower(username),
    71  		Email:       userName,
    72  		Passwd:      password,
    73  		LoginType:   auth_model.SMTP,
    74  		LoginSource: source.authSource.ID,
    75  		LoginName:   userName,
    76  	}
    77  	overwriteDefault := &user_model.CreateUserOverwriteOptions{
    78  		IsActive: util.OptionalBoolTrue,
    79  	}
    80  
    81  	if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
    82  		return user, err
    83  	}
    84  
    85  	return user, nil
    86  }
    87  
    88  // IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
    89  func (source *Source) IsSkipLocalTwoFA() bool {
    90  	return source.SkipLocalTwoFA
    91  }