mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
68 lines
1.7 KiB
Go
68 lines
1.7 KiB
Go
package apiauth
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"errors"
|
|
|
|
"github.com/navidrome/navidrome/model"
|
|
)
|
|
|
|
type Outcome int
|
|
|
|
const (
|
|
NotMine Outcome = iota
|
|
Authenticated
|
|
Rejected
|
|
Unavailable
|
|
)
|
|
|
|
type CredentialResult struct {
|
|
Outcome Outcome
|
|
User *model.User
|
|
Provider string
|
|
PasswordLocal bool
|
|
}
|
|
|
|
type CredentialChecker interface {
|
|
Check(ctx context.Context, username, password string) (CredentialResult, error)
|
|
}
|
|
|
|
// checkCredentials asks each checker in turn; only NotMine moves on, so an owning provider's "no" is final.
|
|
func checkCredentials(ctx context.Context, checkers []CredentialChecker, username, password string) (CredentialResult, error) {
|
|
for _, c := range checkers {
|
|
res, err := c.Check(ctx, username, password)
|
|
if err != nil {
|
|
return CredentialResult{}, err
|
|
}
|
|
switch res.Outcome {
|
|
case NotMine:
|
|
continue
|
|
case Authenticated:
|
|
return res, nil
|
|
case Unavailable:
|
|
return CredentialResult{}, model.ErrNotAvailable
|
|
default:
|
|
return CredentialResult{}, model.ErrInvalidAuth
|
|
}
|
|
}
|
|
return CredentialResult{}, model.ErrInvalidAuth
|
|
}
|
|
|
|
type dbChecker struct {
|
|
ds model.DataStore
|
|
}
|
|
|
|
func (c dbChecker) Check(ctx context.Context, username, password string) (CredentialResult, error) {
|
|
u, err := c.ds.User().FindByUsernameWithPassword(ctx, username)
|
|
if errors.Is(err, model.ErrNotFound) {
|
|
return CredentialResult{Outcome: NotMine}, nil
|
|
}
|
|
if err != nil {
|
|
return CredentialResult{}, err
|
|
}
|
|
if subtle.ConstantTimeCompare([]byte(u.Password), []byte(password)) != 1 {
|
|
return CredentialResult{Outcome: Rejected}, nil
|
|
}
|
|
return CredentialResult{Outcome: Authenticated, User: u, Provider: "password", PasswordLocal: true}, nil
|
|
}
|