mirror of
https://gitlab.crans.org/nounous/ghostream.git
synced 2025-07-01 11:11:13 +02:00
Compare commits
10 Commits
8d2adad509
...
dev
Author | SHA1 | Date | |
---|---|---|---|
3493ba5e2f | |||
f7cf187bac
|
|||
dc594d091c
|
|||
a429216735
|
|||
e6fd4f6352
|
|||
34652f8f3e
|
|||
79f52ed880
|
|||
ee16bf9e21
|
|||
e47aefd6df
|
|||
7e0ee7aba5
|
@ -11,6 +11,13 @@
|
||||
|
||||
This project was developped at [Cr@ns](https://crans.org/) to stream events.
|
||||
|
||||
> **Note**
|
||||
> *This project is no longer maintained!*
|
||||
>
|
||||
> As an alternative, you should try [Galène](https://galene.org/) which supports [WebRTC-HTTP ingestion protocol (WHIP)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/) for low-latency streaming.
|
||||
> OBS Studio introduced WHIP output in version 30.0.
|
||||
> Galène supports WHIP since Galène 0.8.
|
||||
|
||||
Features:
|
||||
|
||||
- WebRTC playback with a lightweight web interface.
|
||||
|
@ -20,7 +20,7 @@ type Options struct {
|
||||
|
||||
// Backend to log user in
|
||||
type Backend interface {
|
||||
Login(string, string) (bool, error)
|
||||
Login(string, string) (bool, string, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
|
@ -23,15 +23,15 @@ type Basic struct {
|
||||
|
||||
// Login hashs password and compare
|
||||
// Returns (true, nil) if success
|
||||
func (a Basic) Login(username string, password string) (bool, error) {
|
||||
func (a Basic) Login(username string, password string) (bool, string, error) {
|
||||
hash, ok := a.Cfg.Credentials[username]
|
||||
if !ok {
|
||||
return false, errors.New("user not found in credentials")
|
||||
return false, "", errors.New("user not found in credentials")
|
||||
}
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
|
||||
// Login succeeded if no error
|
||||
return err == nil, err
|
||||
return err == nil, username, err
|
||||
}
|
||||
|
||||
// Close has no connection to close
|
||||
|
@ -10,19 +10,19 @@ func TestBasicLogin(t *testing.T) {
|
||||
|
||||
// Test good credentials
|
||||
backend, _ := New(&Options{Credentials: basicCredentials})
|
||||
ok, err := backend.Login("demo", "demo")
|
||||
ok, _, err := backend.Login("demo", "demo")
|
||||
if !ok {
|
||||
t.Error("Error while logging with the basic authentication:", err)
|
||||
}
|
||||
|
||||
// Test bad username
|
||||
ok, err = backend.Login("baduser", "demo")
|
||||
ok, _, err = backend.Login("baduser", "demo")
|
||||
if ok {
|
||||
t.Error("Authentification failed to fail:", err)
|
||||
}
|
||||
|
||||
// Test bad password
|
||||
ok, err = backend.Login("demo", "badpass")
|
||||
ok, _, err = backend.Login("demo", "badpass")
|
||||
if ok {
|
||||
t.Error("Authentification failed to fail:", err)
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ package ldap
|
||||
import (
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Options holds package configuration
|
||||
type Options struct {
|
||||
Aliases map[string]string
|
||||
Aliases map[string]map[string]string
|
||||
URI string
|
||||
UserDn string
|
||||
}
|
||||
@ -21,19 +22,37 @@ type LDAP struct {
|
||||
|
||||
// Login tries to bind to LDAP
|
||||
// Returns (true, nil) if success
|
||||
func (a LDAP) Login(username string, password string) (bool, error) {
|
||||
func (a LDAP) Login(username string, password string) (bool, string, error) {
|
||||
aliasSplit := strings.SplitN(username, "__", 2)
|
||||
potentialUsernames := []string{username}
|
||||
|
||||
if len(aliasSplit) == 2 {
|
||||
alias := aliasSplit[0]
|
||||
trueUsername := aliasSplit[1]
|
||||
// Resolve stream alias if necessary
|
||||
for aliasFor, ok := a.Cfg.Aliases[username]; ok; aliasFor, ok = a.Cfg.Aliases[username] {
|
||||
log.Printf("[LDAP] Use stream alias %s for username %s", username, aliasFor)
|
||||
username = aliasFor
|
||||
if aliases, ok := a.Cfg.Aliases[alias]; ok {
|
||||
if _, ok := aliases[trueUsername]; ok {
|
||||
log.Printf("[LDAP] Use stream alias %s for username %s", alias, trueUsername)
|
||||
potentialUsernames = append(potentialUsernames, trueUsername)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error = nil
|
||||
for _, username := range potentialUsernames {
|
||||
// Try to bind as user
|
||||
bindDn := "cn=" + username + "," + a.Cfg.UserDn
|
||||
err := a.Conn.Bind(bindDn, password)
|
||||
|
||||
log.Printf("[LDAP] Logging to %s...", bindDn)
|
||||
err = a.Conn.Bind(bindDn, password)
|
||||
if err == nil {
|
||||
// Login succeeded if no error
|
||||
return err == nil, err
|
||||
return true, aliasSplit[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[LDAP] Logging failed: %s", err)
|
||||
// Unable to log in
|
||||
return err == nil, "", err
|
||||
}
|
||||
|
||||
// Close LDAP connection
|
||||
|
@ -36,8 +36,10 @@ auth:
|
||||
# userdn: cn=users,dc=example,dc=com
|
||||
#
|
||||
# # You can define aliases, to stream on stream.example.com/example with the credentials of the demo account.
|
||||
# # You will have to use the streamid example__demo:password
|
||||
# aliases:
|
||||
# example: demo
|
||||
# example:
|
||||
# demo: ignored
|
||||
#
|
||||
|
||||
## Stream forwarding ##
|
||||
|
@ -42,7 +42,7 @@ func New() *Config {
|
||||
Credentials: make(map[string]string),
|
||||
},
|
||||
LDAP: ldap.Options{
|
||||
Aliases: make(map[string]string),
|
||||
Aliases: make(map[string]map[string]string),
|
||||
URI: "ldap://127.0.0.1:389",
|
||||
UserDn: "cn=users,dc=example,dc=com",
|
||||
},
|
||||
|
@ -79,20 +79,22 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) {
|
||||
|
||||
if len(split) > 1 {
|
||||
// password was provided so it is a streamer
|
||||
name, password := split[0], split[1]
|
||||
name, password := strings.ToLower(split[0]), split[1]
|
||||
if authBackend != nil {
|
||||
// check password
|
||||
if ok, err := authBackend.Login(name, password); !ok || err != nil {
|
||||
ok, username, err := authBackend.Login(name, password)
|
||||
if !ok || err != nil {
|
||||
log.Printf("Failed to authenticate for stream %s", name)
|
||||
s.Close()
|
||||
continue
|
||||
}
|
||||
name = username
|
||||
}
|
||||
|
||||
go handleStreamer(s, streams, name)
|
||||
} else {
|
||||
// password was not provided so it is a viewer
|
||||
name := split[0]
|
||||
name := strings.ToLower(split[0])
|
||||
|
||||
// Send stream
|
||||
go handleViewer(s, streams, name)
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
var (
|
||||
// Precompile regex
|
||||
validPath = regexp.MustCompile("^/[a-z0-9@_-]*$")
|
||||
validPath = regexp.MustCompile("^/[a-zA-Z0-9@_-]*$")
|
||||
|
||||
counterMutex = new(sync.Mutex)
|
||||
connectedClients = make(map[string]map[string]int64)
|
||||
@ -42,7 +42,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Get stream ID from URL, or from domain name
|
||||
path := r.URL.Path[1:]
|
||||
path := strings.ToLower(r.URL.Path[1:])
|
||||
host := r.Host
|
||||
if strings.Contains(host, ":") {
|
||||
realHost, _, err := net.SplitHostPort(r.Host)
|
||||
@ -58,7 +58,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if path == "about" {
|
||||
path = ""
|
||||
} else {
|
||||
path = streamID
|
||||
path = strings.ToLower(streamID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,6 +97,7 @@ func staticHandler() http.Handler {
|
||||
func statisticsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Retrieve stream name from URL
|
||||
name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0]
|
||||
name = strings.ToLower(name)
|
||||
userCount := 0
|
||||
|
||||
// Clients have a unique generated identifier per session, that expires in 40 seconds.
|
||||
|
@ -25,7 +25,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<b>Serveur :</b>
|
||||
<code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?IDENTIFIANT:MOT_DE_PASSE</code>,
|
||||
<code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE</code>,
|
||||
avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code>
|
||||
vos identifiants.
|
||||
</li>
|
||||
|
Reference in New Issue
Block a user