Add infrastructure for external authentication
This commit is contained in:
parent
d67e59658d
commit
63ca247354
21
auth/auth.go
Normal file
21
auth/auth.go
Normal 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
34
auth/internal.go
Normal 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
|
||||||
|
}
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
"git.sr.ht/~emersion/soju"
|
"git.sr.ht/~emersion/soju"
|
||||||
|
"git.sr.ht/~emersion/soju/auth"
|
||||||
"git.sr.ht/~emersion/soju/config"
|
"git.sr.ht/~emersion/soju/config"
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
"git.sr.ht/~emersion/soju/identd"
|
"git.sr.ht/~emersion/soju/identd"
|
||||||
@ -75,6 +76,11 @@ func loadConfig() (*config.Server, *soju.Config, error) {
|
|||||||
motd = strings.TrimSuffix(string(b), "\n")
|
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 {
|
if raw.TLS != nil {
|
||||||
cert, err := tls.LoadX509KeyPair(raw.TLS.CertPath, raw.TLS.KeyPath)
|
cert, err := tls.LoadX509KeyPair(raw.TLS.CertPath, raw.TLS.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -94,6 +100,7 @@ func loadConfig() (*config.Server, *soju.Config, error) {
|
|||||||
DisableInactiveUsersDelay: raw.DisableInactiveUsersDelay,
|
DisableInactiveUsersDelay: raw.DisableInactiveUsersDelay,
|
||||||
EnableUsersOnAuth: raw.EnableUsersOnAuth,
|
EnableUsersOnAuth: raw.EnableUsersOnAuth,
|
||||||
MOTD: motd,
|
MOTD: motd,
|
||||||
|
Auth: auth,
|
||||||
}
|
}
|
||||||
return raw, cfg, nil
|
return raw, cfg, nil
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,10 @@ type MsgStore struct {
|
|||||||
Driver, Source string
|
Driver, Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Driver, Source string
|
||||||
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Listen []string
|
Listen []string
|
||||||
TLS *TLS
|
TLS *TLS
|
||||||
@ -67,6 +71,7 @@ type Server struct {
|
|||||||
|
|
||||||
DB DB
|
DB DB
|
||||||
MsgStore MsgStore
|
MsgStore MsgStore
|
||||||
|
Auth Auth
|
||||||
|
|
||||||
HTTPOrigins []string
|
HTTPOrigins []string
|
||||||
AcceptProxyIPs IPSet
|
AcceptProxyIPs IPSet
|
||||||
@ -91,6 +96,9 @@ func Defaults() *Server {
|
|||||||
MsgStore: MsgStore{
|
MsgStore: MsgStore{
|
||||||
Driver: "memory",
|
Driver: "memory",
|
||||||
},
|
},
|
||||||
|
Auth: Auth{
|
||||||
|
Driver: "internal",
|
||||||
|
},
|
||||||
MaxUserNetworks: -1,
|
MaxUserNetworks: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +157,16 @@ func parse(cfg scfg.Block) (*Server, error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("directive %q: unknown driver %q", d.Name, srv.MsgStore.Driver)
|
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":
|
case "http-origin":
|
||||||
srv.HTTPOrigins = d.Params
|
srv.HTTPOrigins = d.Params
|
||||||
case "accept-proxy-ip":
|
case "accept-proxy-ip":
|
||||||
|
@ -185,6 +185,14 @@ The following directives are supported:
|
|||||||
This can be used together with _disable-inactive-user_ to seamlessly
|
This can be used together with _disable-inactive-user_ to seamlessly
|
||||||
disable and re-enable users during lengthy inactivity.
|
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
|
# IRC SERVICE
|
||||||
|
|
||||||
soju exposes an IRC service called *BouncerServ* to manage the bouncer.
|
soju exposes an IRC service called *BouncerServ* to manage the bouncer.
|
||||||
|
@ -1192,22 +1192,10 @@ func unmarshalUsername(rawUsername string) (username, client, network string) {
|
|||||||
func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
|
func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
|
||||||
username, clientName, networkName := unmarshalUsername(username)
|
username, clientName, networkName := unmarshalUsername(username)
|
||||||
|
|
||||||
u, err := dc.srv.db.GetUser(ctx, username)
|
if err := dc.srv.Config().Auth.AuthPlain(ctx, dc.srv.db, username, password); err != nil {
|
||||||
if err != nil {
|
|
||||||
return newInvalidUsernameOrPasswordError(fmt.Errorf("user not found: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
upgraded, err := u.CheckPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
return newInvalidUsernameOrPasswordError(err)
|
return newInvalidUsernameOrPasswordError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if upgraded {
|
|
||||||
if err := dc.srv.db.StoreUser(ctx, u); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.user = dc.srv.getUser(username)
|
dc.user = dc.srv.getUser(username)
|
||||||
if dc.user == nil {
|
if dc.user == nil {
|
||||||
return fmt.Errorf("user exists in the DB but hasn't been loaded by the bouncer -- a restart may help")
|
return fmt.Errorf("user exists in the DB but hasn't been loaded by the bouncer -- a restart may help")
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"gopkg.in/irc.v4"
|
"gopkg.in/irc.v4"
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
|
|
||||||
|
"git.sr.ht/~emersion/soju/auth"
|
||||||
"git.sr.ht/~emersion/soju/config"
|
"git.sr.ht/~emersion/soju/config"
|
||||||
"git.sr.ht/~emersion/soju/database"
|
"git.sr.ht/~emersion/soju/database"
|
||||||
"git.sr.ht/~emersion/soju/identd"
|
"git.sr.ht/~emersion/soju/identd"
|
||||||
@ -143,6 +144,7 @@ type Config struct {
|
|||||||
UpstreamUserIPs []*net.IPNet
|
UpstreamUserIPs []*net.IPNet
|
||||||
DisableInactiveUsersDelay time.Duration
|
DisableInactiveUsersDelay time.Duration
|
||||||
EnableUsersOnAuth bool
|
EnableUsersOnAuth bool
|
||||||
|
Auth auth.PlainAuthenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@ -186,6 +188,7 @@ func NewServer(db database.Database) *Server {
|
|||||||
srv.config.Store(&Config{
|
srv.config.Store(&Config{
|
||||||
Hostname: "localhost",
|
Hostname: "localhost",
|
||||||
MaxUserNetworks: -1,
|
MaxUserNetworks: -1,
|
||||||
|
Auth: auth.NewInternal(),
|
||||||
})
|
})
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user