Add infrastructure for external authentication

This commit is contained in:
Simon Ser 2022-09-11 15:45:28 +02:00
parent d67e59658d
commit 63ca247354
7 changed files with 92 additions and 13 deletions

21
auth/auth.go Normal file
View file

@ -0,0 +1,21 @@
package auth
import (
"context"
"fmt"
"git.sr.ht/~emersion/soju/database"
)
type PlainAuthenticator interface {
AuthPlain(ctx context.Context, db database.Database, username, password string) error
}
func New(driver, source string) (PlainAuthenticator, error) {
switch driver {
case "internal":
return NewInternal(), nil
default:
return nil, fmt.Errorf("unknown auth driver %q", driver)
}
}

34
auth/internal.go Normal file
View file

@ -0,0 +1,34 @@
package auth
import (
"context"
"fmt"
"git.sr.ht/~emersion/soju/database"
)
type internal struct{}
func NewInternal() PlainAuthenticator {
return internal{}
}
func (internal) AuthPlain(ctx context.Context, db database.Database, username, password string) error {
u, err := db.GetUser(ctx, username)
if err != nil {
return fmt.Errorf("user not found: %w", err)
}
upgraded, err := u.CheckPassword(password)
if err != nil {
return err
}
if upgraded {
if err := db.StoreUser(ctx, u); err != nil {
return err
}
}
return nil
}

View file

@ -23,6 +23,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"git.sr.ht/~emersion/soju"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/config"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/identd"
@ -75,6 +76,11 @@ func loadConfig() (*config.Server, *soju.Config, error) {
motd = strings.TrimSuffix(string(b), "\n")
}
auth, err := auth.New(raw.Auth.Driver, raw.Auth.Source)
if err != nil {
return nil, nil, fmt.Errorf("failed to create authenticator: %v", err)
}
if raw.TLS != nil {
cert, err := tls.LoadX509KeyPair(raw.TLS.CertPath, raw.TLS.KeyPath)
if err != nil {
@ -94,6 +100,7 @@ func loadConfig() (*config.Server, *soju.Config, error) {
DisableInactiveUsersDelay: raw.DisableInactiveUsersDelay,
EnableUsersOnAuth: raw.EnableUsersOnAuth,
MOTD: motd,
Auth: auth,
}
return raw, cfg, nil
}

View file

@ -58,6 +58,10 @@ type MsgStore struct {
Driver, Source string
}
type Auth struct {
Driver, Source string
}
type Server struct {
Listen []string
TLS *TLS
@ -67,6 +71,7 @@ type Server struct {
DB DB
MsgStore MsgStore
Auth Auth
HTTPOrigins []string
AcceptProxyIPs IPSet
@ -91,6 +96,9 @@ func Defaults() *Server {
MsgStore: MsgStore{
Driver: "memory",
},
Auth: Auth{
Driver: "internal",
},
MaxUserNetworks: -1,
}
}
@ -149,6 +157,16 @@ func parse(cfg scfg.Block) (*Server, error) {
default:
return nil, fmt.Errorf("directive %q: unknown driver %q", d.Name, srv.MsgStore.Driver)
}
case "auth":
if err := d.ParseParams(&srv.Auth.Driver); err != nil {
return nil, err
}
switch srv.Auth.Driver {
case "internal":
srv.Auth.Source = ""
default:
return nil, fmt.Errorf("directive %q: unknown driver %q", d.Name, srv.Auth.Driver)
}
case "http-origin":
srv.HTTPOrigins = d.Params
case "accept-proxy-ip":

View file

@ -185,6 +185,14 @@ The following directives are supported:
This can be used together with _disable-inactive-user_ to seamlessly
disable and re-enable users during lengthy inactivity.
*auth* <driver> ...
Set the authentication method. By default, internal authentication is used.
Supported drivers:
*auth internal*
Use internal authentication.
# IRC SERVICE
soju exposes an IRC service called *BouncerServ* to manage the bouncer.

View file

@ -1192,22 +1192,10 @@ func unmarshalUsername(rawUsername string) (username, client, network string) {
func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
username, clientName, networkName := unmarshalUsername(username)
u, err := dc.srv.db.GetUser(ctx, username)
if err != nil {
return newInvalidUsernameOrPasswordError(fmt.Errorf("user not found: %w", err))
}
upgraded, err := u.CheckPassword(password)
if err != nil {
if err := dc.srv.Config().Auth.AuthPlain(ctx, dc.srv.db, username, password); err != nil {
return newInvalidUsernameOrPasswordError(err)
}
if upgraded {
if err := dc.srv.db.StoreUser(ctx, u); err != nil {
return err
}
}
dc.user = dc.srv.getUser(username)
if dc.user == nil {
return fmt.Errorf("user exists in the DB but hasn't been loaded by the bouncer -- a restart may help")

View file

@ -20,6 +20,7 @@ import (
"gopkg.in/irc.v4"
"nhooyr.io/websocket"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/config"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/identd"
@ -143,6 +144,7 @@ type Config struct {
UpstreamUserIPs []*net.IPNet
DisableInactiveUsersDelay time.Duration
EnableUsersOnAuth bool
Auth auth.PlainAuthenticator
}
type Server struct {
@ -186,6 +188,7 @@ func NewServer(db database.Database) *Server {
srv.config.Store(&Config{
Hostname: "localhost",
MaxUserNetworks: -1,
Auth: auth.NewInternal(),
})
return srv
}