mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
refactor: change authentication to return structured data (#12202)
Currently authentication methods return information in two forms: they return who was authenticated as a `*user_model.User`, and then they insert key-values into `ctx.Data` which has critical impact on how the authenticated request is treated. This PR changes the authentication methods to return structured data in the form of an `AuthenticationResult`, with all the key-value information in `ctx.Data` being moved into methods on the `AuthenticationResult` interface. Authentication workflows in Forgejo are a real mess. This is the first step in trying to clean it up and make the code predictable and reasonable, and is both follow-up work that was identified from the repo-specific access tokens (where the `"ApiTokenReducer"` key-value was added), and is pre-requisite work to future JWT enhancements that are [being discussed](https://codeberg.org/forgejo/forgejo/issues/3571#issuecomment-13268004). ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - All changes, at least in theory, are refactors of existing logic and are not expected to have functional deviations -- existing regression tests are the only planned testing. - I ran... - [x] `make pr-go` before pushing ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12202 Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org>
This commit is contained in:
parent
2ed98ac848
commit
1ddd5faa5c
45 changed files with 620 additions and 348 deletions
128
services/auth/method/signin.go
Normal file
128
services/auth/method/signin.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package method
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"forgejo.org/models/auth"
|
||||
"forgejo.org/models/db"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/optional"
|
||||
auth_service "forgejo.org/services/auth"
|
||||
"forgejo.org/services/auth/source/oauth2"
|
||||
"forgejo.org/services/auth/source/smtp"
|
||||
|
||||
_ "forgejo.org/services/auth/source/db" // register the sources (and below)
|
||||
_ "forgejo.org/services/auth/source/ldap" // register the ldap source
|
||||
_ "forgejo.org/services/auth/source/pam" // register the pam source
|
||||
)
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(ctx context.Context, username, password string) (*user_model.User, *auth.Source, error) {
|
||||
var user *user_model.User
|
||||
isEmail := false
|
||||
if strings.Contains(username, "@") {
|
||||
isEmail = true
|
||||
emailAddress := user_model.EmailAddress{LowerEmail: strings.ToLower(strings.TrimSpace(username))}
|
||||
// check same email
|
||||
has, err := db.GetEngine(ctx).Get(&emailAddress)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if has {
|
||||
if !emailAddress.IsActivated {
|
||||
return nil, nil, user_model.ErrEmailAddressNotExist{
|
||||
Email: username,
|
||||
}
|
||||
}
|
||||
user = &user_model.User{ID: emailAddress.UID}
|
||||
}
|
||||
} else {
|
||||
trimmedUsername := strings.TrimSpace(username)
|
||||
if len(trimmedUsername) == 0 {
|
||||
return nil, nil, user_model.ErrUserNotExist{Name: username}
|
||||
}
|
||||
|
||||
user = &user_model.User{LowerName: strings.ToLower(trimmedUsername)}
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
hasUser, err := user_model.GetUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if hasUser {
|
||||
source, err := auth.GetSourceByID(ctx, user.LoginSource)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !source.IsActive {
|
||||
return nil, nil, oauth2.ErrAuthSourceNotActivated
|
||||
}
|
||||
|
||||
authenticator, ok := source.Cfg.(auth_service.PasswordAuthenticator)
|
||||
if !ok {
|
||||
return nil, nil, smtp.ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
user, err := authenticator.Authenticate(ctx, user, user.LoginName, password)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
|
||||
// user could be hint to resend confirm email.
|
||||
if user.ProhibitLogin {
|
||||
return nil, nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name}
|
||||
}
|
||||
|
||||
return user, source, nil
|
||||
}
|
||||
}
|
||||
|
||||
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
|
||||
IsActive: optional.Some(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if !source.IsActive {
|
||||
// don't try to authenticate non-active sources
|
||||
continue
|
||||
}
|
||||
|
||||
authenticator, ok := source.Cfg.(auth_service.PasswordAuthenticator)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
authUser, err := authenticator.Authenticate(ctx, nil, username, password)
|
||||
|
||||
if err == nil {
|
||||
if !authUser.ProhibitLogin {
|
||||
return authUser, source, nil
|
||||
}
|
||||
err = user_model.ErrUserProhibitLogin{UID: authUser.ID, Name: authUser.Name}
|
||||
}
|
||||
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
log.Debug("Failed to login '%s' via '%s': %v", username, source.Name, err)
|
||||
} else {
|
||||
log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if isEmail {
|
||||
return nil, nil, user_model.ErrEmailAddressNotExist{Email: username}
|
||||
}
|
||||
|
||||
return nil, nil, user_model.ErrUserNotExist{Name: username}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue