Rebrand project as Skald

This commit is contained in:
Storm Dragon
2026-05-17 22:16:08 -04:00
parent 77edf0539c
commit bda0e548d3
53 changed files with 479 additions and 433 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
*~ *~
/galene /skald
/galenectl/galenectl /skaldctl/skaldctl
/data /data
/groups/**/*.json /groups/**/*.json
/static/**/*.d.ts /static/**/*.d.ts
+6
View File
@@ -1,3 +1,9 @@
Skald 0.1 (unreleased):
* Forked from Galene after commit f718385.
* Renamed the project, module, command, control tool, static assets, and
documentation entrypoints from Galene to Skald.
Galene 1.1 (unreleased): Galene 1.1 (unreleased):
* Implemented "galenectl initial-setup". * Implemented "galenectl initial-setup".
+22 -34
View File
@@ -1,59 +1,47 @@
# The Galene videoconferencing system # Skald
Galene is a fully-features videoconferencing system that is easy to deploy Skald is a hard fork of Galene that is being turned into an audio-only
and requires very moderate server resources. It is described at hall conferencing server. The current development repository is
<https://galene.org>. <https://git.stormux.org/storm/skald>.
## Quick start ## Quick start
```sh ```sh
git clone https://github.com/jech/galene git clone https://git.stormux.org/storm/skald
cd galene cd skald
CGO_ENABLED=0 go build -ldflags='-s -w' CGO_ENABLED=0 go build -ldflags='-s -w'
mkdir groups mkdir groups
echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json
./galene & ./skald &
``` ```
Point your browser at <https://localhost:8443/group/night-watch/>, ignore Point your browser at <https://localhost:8443/group/night-watch/>, ignore
the unknown certificate warning, and log in with username *vimes* and the unknown certificate warning, and log in with username *vimes* and
password *sybil*. password *sybil*.
For full installation instructions, please see the file [galene-install.md][1] For full installation instructions, please see the file [skald-install.md][1]
in this directory. in this directory.
## Documentation ## Documentation
* [galene-install.md][1]: full installation instructions * [skald-install.md][1]: full installation instructions
* [galene.md][2]: usage and administration; * [skald.md][2]: usage and administration;
* [galene-client.md][3]: writing clients; * [skald-client.md][3]: writing clients;
* [galene-protocol.md][4]: the client protocol; * [skald-protocol.md][4]: the client protocol;
* [galene-api.md][5]: Galene's administrative API. * [skald-api.md][5]: Skald's administrative API.
## Contributing ## Contributing
In order to contribute to Galene, you may: Skald is currently being developed at
<https://git.stormux.org/storm/skald>.
* send patches to [the Galene mailing list][6] by sending mail to
<galene@lists.galene.org>;
* submit pull requests on GitHub.
For general discussion, please use the [Galene mailing list][6] (feel free
to send mail without subscribing). Please do not use Github for general
discussion.
## Further information ## Further information
Galène's web page is at <https://galene.org>. Skald is derived from Galene by Juliusz Chroboczek. Galene's web page is at
<https://galene.org>.
Answers to common questions and issues are at <https://galene.org/faq.html>. [1]: <skald-install.md>
[2]: <skald.md>
[3]: <skald-client.md>
-- Juliusz Chroboczek <https://www.irif.fr/~jch/> [4]: <skald-protocol.md>
[5]: <skald-api.md>
[1]: <galene-install.md>
[2]: <galene.md>
[3]: <galene-client.md>
[4]: <galene-protocol.md>
[5]: <galene-api.md>
[6]: <https://lists.galene.org/>
+4 -4
View File
@@ -22,10 +22,10 @@ import (
"github.com/jech/samplebuilder" "github.com/jech/samplebuilder"
gcodecs "github.com/jech/galene/codecs" gcodecs "git.stormux.org/storm/skald/codecs"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
const ( const (
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
func TestSanitise(t *testing.T) { func TestSanitise(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
type Estimator struct { type Estimator struct {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
func TestEstimator(t *testing.T) { func TestEstimator(t *testing.T) {
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/jech/galene module git.stormux.org/storm/skald
go 1.21 go 1.21
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
) )
type RawPassword struct { type RawPassword struct {
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
var ErrTagMismatch = errors.New("tag mismatch") var ErrTagMismatch = errors.New("tag mismatch")
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"github.com/pion/interceptor" "github.com/pion/interceptor"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
var Directory, DataDirectory string var Directory, DataDirectory string
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/turnserver" "git.stormux.org/storm/skald/turnserver"
) )
type timeoutError struct{} type timeoutError struct{}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/turnserver" "git.stormux.org/storm/skald/turnserver"
) )
func TestPassword(t *testing.T) { func TestPassword(t *testing.T) {
+1 -1
View File
@@ -4,7 +4,7 @@ package jitter
import ( import (
"sync/atomic" "sync/atomic"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
type Estimator struct { type Estimator struct {
+10 -10
View File
@@ -14,16 +14,16 @@ import (
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/codecs" "git.stormux.org/storm/skald/codecs"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
"github.com/jech/galene/estimator" "git.stormux.org/storm/skald/estimator"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/ice" "git.stormux.org/storm/skald/ice"
"github.com/jech/galene/jitter" "git.stormux.org/storm/skald/jitter"
"github.com/jech/galene/packetcache" "git.stormux.org/storm/skald/packetcache"
"github.com/jech/galene/packetmap" "git.stormux.org/storm/skald/packetmap"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
"github.com/jech/galene/unbounded" "git.stormux.org/storm/skald/unbounded"
) )
type bitrate struct { type bitrate struct {
+1 -1
View File
@@ -3,7 +3,7 @@ package rtpconn
import ( import (
"testing" "testing"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
func TestDownTrackAtomics(t *testing.T) { func TestDownTrackAtomics(t *testing.T) {
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/codecs" "git.stormux.org/storm/skald/codecs"
"github.com/jech/galene/packetcache" "git.stormux.org/storm/skald/packetcache"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
func readLoop(track *rtpUpTrack) { func readLoop(track *rtpUpTrack) {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"sort" "sort"
"time" "time"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
"github.com/jech/galene/stats" "git.stormux.org/storm/skald/stats"
) )
func (c *webClient) GetStats() *stats.Client { func (c *webClient) GetStats() *stats.Client {
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"sort" "sort"
"time" "time"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
"github.com/jech/galene/packetcache" "git.stormux.org/storm/skald/packetcache"
"github.com/jech/galene/rtptime" "git.stormux.org/storm/skald/rtptime"
) )
// packetIndex is a request to send a packet from the cache. // packetIndex is a request to send a packet from the cache.
+7 -7
View File
@@ -17,13 +17,13 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
"github.com/jech/galene/diskwriter" "git.stormux.org/storm/skald/diskwriter"
"github.com/jech/galene/estimator" "git.stormux.org/storm/skald/estimator"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/ice" "git.stormux.org/storm/skald/ice"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
"github.com/jech/galene/unbounded" "git.stormux.org/storm/skald/unbounded"
) )
func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) { func errorToWSCloseMessage(id string, err error) (*clientMessage, []byte) {
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
var tokens = []string{ var tokens = []string{
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"net" "net"
"sync" "sync"
"github.com/jech/galene/conn" "git.stormux.org/storm/skald/conn"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/sdpfrag" "git.stormux.org/storm/skald/sdpfrag"
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
+21 -21
View File
@@ -1,9 +1,9 @@
# Galene's administrative API # Skald's administrative API
Galene provides an HTTP-based API that can be used to create groups and Skald provides an HTTP-based API that can be used to create groups and
users. For example, in order to create a group, a client may do users. For example, in order to create a group, a client may do
PUT /galene-api/v0/.groups/groupname/ PUT /skald-api/v0/.groups/groupname/
Content-Type: application/json Content-Type: application/json
If-None-Match: * If-None-Match: *
@@ -11,12 +11,12 @@ The `If-None-Match` header avoids overwriting an existing group.
In order to edit a group definition, a client first does In order to edit a group definition, a client first does
GET /galene-api/v0/.groups/groupname/ GET /skald-api/v0/.groups/groupname/
This yields the group definition and an entity tag (in the ETag header). This yields the group definition and an entity tag (in the ETag header).
The client then modifies the group defintion, and does The client then modifies the group defintion, and does
PUT /galene-api/v0/.groups/groupname/ PUT /skald-api/v0/.groups/groupname/
If-Match: "abcd" If-Match: "abcd"
where "abcd" is the entity tag returned by the GET request. If the group where "abcd" is the entity tag returned by the GET request. If the group
@@ -27,13 +27,13 @@ in the case of a concurrent modification.
## Endpoints ## Endpoints
The API is located under `/galene-api/v0/`. The `/v0/` is a version number, The API is located under `/skald-api/v0/`. The `/v0/` is a version number,
and will be incremented if we ever find out that the current API cannot be and will be incremented if we ever find out that the current API cannot be
extended in a backwards compatible manner. extended in a backwards compatible manner.
### Statistics ### Statistics
/galene-api/v0/.stats /skald-api/v0/.stats
Provides a number of statistics about the running server, in JSON. The Provides a number of statistics about the running server, in JSON. The
exact format is undocumented, and may change between versions. The only exact format is undocumented, and may change between versions. The only
@@ -41,14 +41,14 @@ allowed methods are HEAD and GET.
### List of groups ### List of groups
/galene-api/v0/.groups/ /skald-api/v0/.groups/
Returns a list of groups, as a JSON array. The only allowed methods are Returns a list of groups, as a JSON array. The only allowed methods are
HEAD and GET. HEAD and GET.
### Group definition ### Group definition
/galene-api/v0/.groups/groupname /skald-api/v0/.groups/groupname
Contains a "sanitised" group definition in JSON format, analogous to the Contains a "sanitised" group definition in JSON format, analogous to the
on-disk format but without any user definitions or cryptographic keys. on-disk format but without any user definitions or cryptographic keys.
@@ -57,7 +57,7 @@ content-type is `application/json`.
### Authentication keys ### Authentication keys
/galene-api/v0/.groups/groupname/.keys /skald-api/v0/.groups/groupname/.keys
Contains the keys used for validation of stateless tokens, encoded as Contains the keys used for validation of stateless tokens, encoded as
a JSON key set (RFC 7517). Allowed methods are PUT and DELETE. The only a JSON key set (RFC 7517). Allowed methods are PUT and DELETE. The only
@@ -65,16 +65,16 @@ accepted content-type is `application/jwk-set+json`.
### List of users ### List of users
/galene-api/v0/.groups/groupname/.users/ /skald-api/v0/.groups/groupname/.users/
Returns a list of users, as a JSON array. The only allowed methods are Returns a list of users, as a JSON array. The only allowed methods are
HEAD and GET. HEAD and GET.
### User definitions ### User definitions
/galene-api/v0/.groups/groupname/.users/username /skald-api/v0/.groups/groupname/.users/username
/galene-api/v0/.groups/groupname/.empty-user /skald-api/v0/.groups/groupname/.empty-user
/galene-api/v0/.groups/groupname/.wildcard-user /skald-api/v0/.groups/groupname/.wildcard-user
Contains a "sanitised" user definition (without any passwords), a JSON Contains a "sanitised" user definition (without any passwords), a JSON
object with a single field `permissions`. The entries `.empty-user` and object with a single field `permissions`. The entries `.empty-user` and
@@ -84,9 +84,9 @@ only accepted content-type is `application/json`.
### Passwords ### Passwords
/galene-api/v0/.groups/groupname/.users/username/.password /skald-api/v0/.groups/groupname/.users/username/.password
/galene-api/v0/.groups/groupname/.empty-user/.password /skald-api/v0/.groups/groupname/.empty-user/.password
/galene-api/v0/.groups/groupname/.wildcard-user/.password /skald-api/v0/.groups/groupname/.wildcard-user/.password
Contains the password of a given user. The PUT method takes a full Contains the password of a given user. The PUT method takes a full
password definition, identical to what can appear in the `"password"` password definition, identical to what can appear in the `"password"`
@@ -97,7 +97,7 @@ POST.
### Wildcard user ### Wildcard user
/galene-api/v0/.groups/groupname/.wildcard-user /skald-api/v0/.groups/groupname/.wildcard-user
Contains a dictionary defining the wildcard user, in the same format as Contains a dictionary defining the wildcard user, in the same format as
the dictionary defining an ordinary user. Allowed methods are HEAD, GET, the dictionary defining an ordinary user. Allowed methods are HEAD, GET,
@@ -105,14 +105,14 @@ PUT and DELETE.
### Wildcard user password ### Wildcard user password
/galene-api/v0/.groups/groupname/.wildcard-user/.password /skald-api/v0/.groups/groupname/.wildcard-user/.password
This is analogous to the password of an ordinary user. Allowed methods This is analogous to the password of an ordinary user. Allowed methods
are PUT, POST and DELETE. are PUT, POST and DELETE.
### List of stateful tokens ### List of stateful tokens
/galene-api/v0/.groups/groupname/.users/username/.tokens/ /skald-api/v0/.groups/groupname/.users/username/.tokens/
GET returns the list of stateful tokens, as a JSON array. POST creates GET returns the list of stateful tokens, as a JSON array. POST creates
a new token, and returns its name in the `Location` header. Allowed a new token, and returns its name in the `Location` header. Allowed
@@ -120,7 +120,7 @@ methods are HEAD, GET and POST.
### Stateful token ### Stateful token
/galene-api/v0/.groups/groupname/.users/username/.tokens/token /skald-api/v0/.groups/groupname/.users/username/.tokens/token
The full contents of a single token, in JSON. The exact format may change The full contents of a single token, in JSON. The exact format may change
between versions, so a client should first GET a token, update one or more between versions, so a client should first GET a token, update one or more
+3 -3
View File
@@ -4,11 +4,11 @@ The frontend is written in JavaScript and is split into two files:
- `protocol.js` contains the low-level functions that interact with the - `protocol.js` contains the low-level functions that interact with the
server; server;
- `galene.js` contains the user interface. - `skald.js` contains the user interface.
A simpler example client can be found in the directory `static/example`. A simpler example client can be found in the directory `static/example`.
A new frontend may either implement Galène's client-server protocol from A new frontend may either implement Skald's client-server protocol from
scratch, or it may use the functionality of `protocol.js`. This document scratch, or it may use the functionality of `protocol.js`. This document
documents the latter approach. documents the latter approach.
@@ -197,7 +197,7 @@ audio energy (the square of the volume) for streams in the down direction.
# Peer-to-peer file transfer # Peer-to-peer file transfer
Galene's client allows users to transfer files during a meeting. The Skald's client allows users to transfer files during a meeting. The
protocol is peer-to-peer: the clients exchange network parameters and protocol is peer-to-peer: the clients exchange network parameters and
cryptographic keys through the server (over messages of type cryptographic keys through the server (over messages of type
`usermessage`), but all file transfer is performed directly between the `usermessage`), but all file transfer is performed directly between the
+62 -62
View File
@@ -1,8 +1,8 @@
# Galene installation instructions # Skald installation instructions
## Basic installation ## Basic installation
### Build the Galene binary ### Build the Skald binary
Say: Say:
@@ -28,8 +28,8 @@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags='-s -w'
### Optional: install libraries for background blur ### Optional: install libraries for background blur
Galene's client uses Google's MediaPipe library to implement background Skald's client uses Google's MediaPipe library to implement background
blur. This library is optional, and if it is absent, Galene will blur. This library is optional, and if it is absent, Skald will
disable the menu entries for background blur. disable the menu entries for background blur.
Optionally install Google's MediaPipe library: Optionally install Google's MediaPipe library:
@@ -53,8 +53,8 @@ If you don't have `wget` on your system, try using `curl -O` instead.
### Deploy to your server ### Deploy to your server
The following instructions assume that your server is called The following instructions assume that your server is called
`galene.example.org` and that you have already created a dedicated user `skald.example.org` and that you have already created a dedicated user
called `galene`. called `skald`.
First, make sure that the `groups` and `data` directories exist: First, make sure that the `groups` and `data` directories exist:
@@ -62,37 +62,37 @@ First, make sure that the `groups` and `data` directories exist:
mkdir groups data mkdir groups data
``` ```
Now copy the `galene` binary, and the directories `static`, `data` and Now copy the `skald` binary, and the directories `static`, `data` and
`groups` to the server: `groups` to the server:
```sh ```sh
rsync -a galene static data groups galene@galene.example.org: rsync -a skald static data groups skald@skald.example.org:
``` ```
If you don't have a TLS certificate, Galene will generate a self-signed If you don't have a TLS certificate, Skald will generate a self-signed
certificate (and print a warning to the logs). If you have a certificate, certificate (and print a warning to the logs). If you have a certificate,
install it in the files `data/cert.pem` and `data/key.pem`: install it in the files `data/cert.pem` and `data/key.pem`:
```sh ```sh
ssh galene@galene.example.org ssh skald@skald.example.org
sudo cp /etc/letsencrypt/live/galene.example.org/fullchain.pem data/cert.pem sudo cp /etc/letsencrypt/live/skald.example.org/fullchain.pem data/cert.pem
sudo cp /etc/letsencrypt/live/galene.example.org/privkey.pem data/key.pem sudo cp /etc/letsencrypt/live/skald.example.org/privkey.pem data/key.pem
sudo chown galene:galene data/*.pem sudo chown skald:skald data/*.pem
chmod go-rw data/key.pem chmod go-rw data/key.pem
``` ```
Since certificates are regularly rotated, this should be done in a monthly Since certificates are regularly rotated, this should be done in a monthly
cron job (or a *SystemD* timer unit, if you're feeling particularly kinky). cron job (or a *SystemD* timer unit, if you're feeling particularly kinky).
### Run Galene on the server ### Run Skald on the server
Arrange to run the binary on the server. If you never reboot your server, Arrange to run the binary on the server. If you never reboot your server,
just do: just do:
```sh ```sh
ssh galene@galene.example.org ssh skald@skald.example.org
ulimit -n 65536 ulimit -n 65536
nohup ./galene & nohup ./skald &
``` ```
If you are using *runit*, use a script like the following: If you are using *runit*, use a script like the following:
@@ -100,58 +100,58 @@ If you are using *runit*, use a script like the following:
```sh ```sh
#!/bin/sh #!/bin/sh
exec 2>&1 exec 2>&1
cd ~galene cd ~skald
ulimit -n 65536 ulimit -n 65536
exec setuidgid galene ./galene exec setuidgid skald ./skald
``` ```
If you are using *SystemD*, put the following in If you are using *SystemD*, put the following in
`/etc/systemd/system/galene.service` (and then run `systemctl daemon-reload`): `/etc/systemd/system/skald.service` (and then run `systemctl daemon-reload`):
```ini ```ini
[Unit] [Unit]
Description=Galene Description=Skald
After=network.target After=network.target
[Service] [Service]
Type=simple Type=simple
WorkingDirectory=/home/galene WorkingDirectory=/home/skald
User=galene User=skald
Group=galene Group=skald
ExecStart=/home/galene/galene ExecStart=/home/skald/skald
LimitNOFILE=65536 LimitNOFILE=65536
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
### Set up galenectl ### Set up skaldctl
There are two ways to administer a Galene instance: by manually editing There are two ways to administer a Skald instance: by manually editing
JSON files on the server, or by using the `galenectl` utility. JSON files on the server, or by using the `skaldctl` utility.
The `galenectl` utility is recommended, since it avoids issues with The `skaldctl` utility is recommended, since it avoids issues with
concurrent modifications and is less error-prone than the alternative. concurrent modifications and is less error-prone than the alternative.
Build the `galenectl` utility, and copy it somewhere on your path: Build the `skaldctl` utility, and copy it somewhere on your path:
```sh ```sh
cd galenectl cd skaldctl
go build -ldflags='-s -w' go build -ldflags='-s -w'
sudo cp galenectl /usr/local/bin/ sudo cp skaldctl /usr/local/bin/
``` ```
Now create an administrator password, and set up galenectl: Now create an administrator password, and set up skaldctl:
```sh ```sh
galenectl -admin-username admin initial-setup skaldctl -admin-username admin initial-setup
``` ```
This command creates two files: `galenectl.json` and `config.json`. The This command creates two files: `skaldctl.json` and `config.json`. The
former is already at the right place, the latter must be copied to the former is already at the right place, the latter must be copied to the
server's `data/` directory: server's `data/` directory:
```sh ```sh
rsync config.json galene@galene.example.org:data/ rsync config.json skald@skald.example.org:data/
``` ```
### Group setup ### Group setup
@@ -159,51 +159,51 @@ rsync config.json galene@galene.example.org:data/
Create a group: Create a group:
```sh ```sh
galenectl create-group -group city-watch skaldctl create-group -group city-watch
``` ```
If you didn't install a TLS certificate above, you will need to run If you didn't install a TLS certificate above, you will need to run
`galenectl` with the flag `-insecure`: `skaldctl` with the flag `-insecure`:
```sh ```sh
galenectl -insecure create-group -group city-watch skaldctl -insecure create-group -group city-watch
``` ```
Create an "op", a user with group moderation privileges: Create an "op", a user with group moderation privileges:
```sh ```sh
galenectl create-user -group city-watch -user vimes -permissions op skaldctl create-user -group city-watch -user vimes -permissions op
``` ```
Set the new user's password: Set the new user's password:
```sh ```sh
galenectl set-password -group city-watch -user vimes skaldctl set-password -group city-watch -user vimes
``` ```
You should now be able to test your Galene installation by pointing a web You should now be able to test your Skald installation by pointing a web
browser at <https://galene.example.org:8443/group/city-watch/>. browser at <https://skald.example.org:8443/group/city-watch/>.
Create an ordinary user: Create an ordinary user:
```sh ```sh
galenectl create-user -group city-watch -user fred skaldctl create-user -group city-watch -user fred
galenectl set-password -group city-watch -user fred skaldctl set-password -group city-watch -user fred
``` ```
Check the results: Check the results:
```sh ```sh
galenectl list-groups skaldctl list-groups
galenectl list-users -l -group city-watch skaldctl list-users -l -group city-watch
``` ```
Type `galenectl -help`, `galenectl create-group -help`, etc. for more Type `skaldctl -help`, `skaldctl create-group -help`, etc. for more
information. information.
## Advanced configuration ## Advanced configuration
Galene is designed to be exposed directly to the Internet. If your server Skald is designed to be exposed directly to the Internet. If your server
is behind a firewall or NAT router, some extra configuration is necessary. is behind a firewall or NAT router, some extra configuration is necessary.
### Running behind a firewall ### Running behind a firewall
@@ -229,7 +229,7 @@ of the following options:
and demultiplex the traffic in userspace. and demultiplex the traffic in userspace.
At the time of writing, this mechanism is not quite complete, and you will At the time of writing, this mechanism is not quite complete, and you will
see Galene attempting to use other ports. Unless you see connection see Skald attempting to use other ports. Unless you see connection
failures, this is nothing to worry about. failures, this is nothing to worry about.
### Running behind NAT ### Running behind NAT
@@ -238,23 +238,23 @@ If your server is behind NAT, then currently the only option is to use
a STUN, or, preferably, TURN server on a separate host, one that is not a STUN, or, preferably, TURN server on a separate host, one that is not
behind NAT. See Section *Connectivity issues and ICE servers* below. behind NAT. See Section *Connectivity issues and ICE servers* below.
Galene has some support for running behind NAT without a helpful server, Skald has some support for running behind NAT without a helpful server,
but this has not been exhaustively tested. Please see the section but this has not been exhaustively tested. Please see the section
"Connectivity issues and ICE servers" below. "Connectivity issues and ICE servers" below.
### Running behind a reverse proxy ### Running behind a reverse proxy
Galene is designed to be directly exposed to the Internet. In order to Skald is designed to be directly exposed to the Internet. In order to
run Galene behind a reverse proxy, you might need to make a number of run Skald behind a reverse proxy, you might need to make a number of
tweaks to your configuration. tweaks to your configuration.
First, you might need to inform Galene of the URL at which users connect First, you might need to inform Skald of the URL at which users connect
(the reverse proxy's URL) by adding an entry `proxyURL` to your (the reverse proxy's URL) by adding an entry `proxyURL` to your
`data/config.json` file: `data/config.json` file:
```json ```json
{ {
"proxyURL": "https://galene.example.org/" "proxyURL": "https://skald.example.org/"
} }
``` ```
@@ -272,12 +272,12 @@ location /ws {
``` ```
Finally, in order to avoid TLS termination issues, you may want to run Finally, in order to avoid TLS termination issues, you may want to run
Galene over plain HTTP instead of HTTPS by using the command-line flag Skald over plain HTTP instead of HTTPS by using the command-line flag
`-insecure`. `-insecure`.
Note that even if you're using a reverse proxy, clients will attempt to Note that even if you're using a reverse proxy, clients will attempt to
establish direct UDP flows with Galene and direct TCP connections to establish direct UDP flows with Skald and direct TCP connections to
Galene's TURN server; see the section *Configuring your firewall* Skald's TURN server; see the section *Configuring your firewall*
above. above.
## Connectivity issues and ICE servers ## Connectivity issues and ICE servers
@@ -290,7 +290,7 @@ that help punching holes in well-behaved NATs, and TURN servers, that
serve as relays for traffic. TURN is a superset of STUN: no STUN server serve as relays for traffic. TURN is a superset of STUN: no STUN server
is necessary if one or more TURN servers are available. is necessary if one or more TURN servers are available.
Galene includes an IPv4-only TURN server, which is controlled by the Skald includes an IPv4-only TURN server, which is controlled by the
`-turn` command-line option. It has the following behaviour: `-turn` command-line option. It has the following behaviour:
* if its value is set to the empty string `""`, then the built-in server * if its value is set to the empty string `""`, then the built-in server
@@ -314,7 +314,7 @@ Galene includes an IPv4-only TURN server, which is controlled by the
If the server is not accessible from the Internet, e.g. because of NAT or If the server is not accessible from the Internet, e.g. because of NAT or
because it is behind a restrictive firewall, then you should configure because it is behind a restrictive firewall, then you should configure
a TURN server that runs on a host that is accessible by both Galene and a TURN server that runs on a host that is accessible by both Skald and
the clients. Disable the built-in TURN server (`-turn ""` or the default the clients. Disable the built-in TURN server (`-turn ""` or the default
`-turn auto`), and provide a working ICE configuration in the file `-turn auto`), and provide a working ICE configuration in the file
`data/ice-servers.json`. In the case of a single STUN server, it should `data/ice-servers.json`. In the case of a single STUN server, it should
@@ -340,7 +340,7 @@ look like this:
"turn:turn.example.org:443", "turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp" "turn:turn.example.org:443?transport=tcp"
], ],
"username": "galene", "username": "skald",
"credential": "secret" "credential": "secret"
} }
] ]
@@ -356,7 +356,7 @@ that, then the `ice-servers.json` file should look like this:
"turn:turn.example.org:443", "turn:turn.example.org:443",
"turn:turn.example.org:443?transport=tcp" "turn:turn.example.org:443?transport=tcp"
], ],
"username": "galene", "username": "skald",
"credential": "secret", "credential": "secret",
"credentialType": "hmac-sha1" "credentialType": "hmac-sha1"
} }
@@ -365,5 +365,5 @@ that, then the `ice-servers.json` file should look like this:
For redundancy, you may set up multiple TURN servers, and ICE will use the For redundancy, you may set up multiple TURN servers, and ICE will use the
first one that works. If an `ice-servers.json` file is present and first one that works. If an `ice-servers.json` file is present and
Galene's built-in TURN server is enabled, then the external server will be Skald's built-in TURN server is enabled, then the external server will be
used in preference to the built-in server. used in preference to the built-in server.
+15 -15
View File
@@ -1,4 +1,4 @@
# Galène's protocol # Skald's protocol
## Data structures ## Data structures
@@ -17,12 +17,12 @@ that do not originate messages (servers) do not need to be assigned an id.
### Stream ### Stream
A stream is a set of related tracks. It is identified by an id, an opaque A stream is a set of related tracks. It is identified by an id, an opaque
string. Streams in Galène are unidirectional. A stream is carried by string. Streams in Skald are unidirectional. A stream is carried by
exactly one peer connection (PC) (multiple streams in a single PC are not exactly one peer connection (PC) (multiple streams in a single PC are not
allowed). The offerer is also the RTP sender (i.e. all tracks sent by the allowed). The offerer is also the RTP sender (i.e. all tracks sent by the
offerer are of type `sendonly`). offerer are of type `sendonly`).
Galène uses a symmetric, asynchronous protocol. In client-server Skald uses a symmetric, asynchronous protocol. In client-server
usage, some messages are only sent in the client to server or in the usage, some messages are only sent in the client to server or in the
server to client direction. server to client direction.
@@ -53,7 +53,7 @@ All fields are optional except `name`, `location` and `endpoint`.
## Connecting ## Connecting
The client connects to the websocket at the URL obtained at the previous The client connects to the websocket at the URL obtained at the previous
step. Galene uses a symmetric, asynchronous protocol: there are no step. Skald uses a symmetric, asynchronous protocol: there are no
requests and responses, and most messages may be sent by either peer. requests and responses, and most messages may be sent by either peer.
## Message syntax ## Message syntax
@@ -227,7 +227,7 @@ The field `label` is one of `camera`, `screenshare` or `video`, and will
be matched against the keys sent by the receiver in their `request` message. be matched against the keys sent by the receiver in their `request` message.
The field `sdp` contains the raw SDP string (i.e. the `sdp` field of The field `sdp` contains the raw SDP string (i.e. the `sdp` field of
a JSEP session description). Galène will interpret the `nack`, a JSEP session description). Skald will interpret the `nack`,
`nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act `nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act
accordingly. accordingly.
@@ -541,13 +541,13 @@ receives the `cancel` message immediately tears down the peer connection
# Authorisation protocol # Authorisation protocol
In addition to username/password authentication, Galene supports In addition to username/password authentication, Skald supports
authentication using cryptographic tokens. Two flows are supported: using authentication using cryptographic tokens. Two flows are supported: using
an authentication server, where Galene's client requests a token from an authentication server, where Skald's client requests a token from
a third-party server, and using an authentication portal, where a third-party server, and using an authentication portal, where
a third-party login portal redirects the user to Galene. Authentication a third-party login portal redirects the user to Skald. Authentication
servers are somewhat simpler to implement, but authentication portals are servers are somewhat simpler to implement, but authentication portals are
more flexible and avoid communicating the user's password to Galene's more flexible and avoid communicating the user's password to Skald's
Javascript code. Javascript code.
## Authentication server ## Authentication server
@@ -558,24 +558,24 @@ a POST request to the authorisation server URL containing in its body
a JSON dictionary of the following form: a JSON dictionary of the following form:
```javascript ```javascript
{ {
"location": "https://galene.example.org/group/groupname/", "location": "https://skald.example.org/group/groupname/",
"username": username, "username": username,
"password": password "password": password
} }
``` ```
If the user is not allowed to join the group, then the authorisation If the user is not allowed to join the group, then the authorisation
server replies with a code of 403 ("not authorised"), and Galene will server replies with a code of 403 ("not authorised"), and Skald will
reject the user. If the authentication server has no opinion about reject the user. If the authentication server has no opinion about
whether the user is allowed to join, it replies with a code of 204 ("no whether the user is allowed to join, it replies with a code of 204 ("no
content"), and Galene will proceed with ordinary password authorisation. content"), and Skald will proceed with ordinary password authorisation.
If the user is allowed to join, then the authorisation server replies with If the user is allowed to join, then the authorisation server replies with
a signed JWT (a "JWS") the body of which has the following form: a signed JWT (a "JWS") the body of which has the following form:
```javascript ```javascript
{ {
"sub": username, "sub": username,
"aud": "https://galene.example.org/group/groupname/", "aud": "https://skald.example.org/group/groupname/",
"permissions": ["present"], "permissions": ["present"],
"iat": now, "iat": now,
"exp": now + 30s, "exp": now + 30s,
@@ -593,10 +593,10 @@ a stateful token.
## Authentication portal ## Authentication portal
If a group's status dictionary has a non-empty `authPortal` field, Galene If a group's status dictionary has a non-empty `authPortal` field, Skald
redirects the user agent to the URL indicated by `authPortal`. The redirects the user agent to the URL indicated by `authPortal`. The
authentication portal performs authorisation, generates a token as above, authentication portal performs authorisation, generates a token as above,
then redirects back to the group's URL with the token stores in a URL then redirects back to the group's URL with the token stores in a URL
query parameter named `token`: query parameter named `token`:
https://galene.example.org/group/groupname/?token=eyJhbG... https://skald.example.org/group/groupname/?token=eyJhbG...
+7 -7
View File
@@ -14,13 +14,13 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/jech/galene/diskwriter" "git.stormux.org/storm/skald/diskwriter"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/ice" "git.stormux.org/storm/skald/ice"
"github.com/jech/galene/limit" "git.stormux.org/storm/skald/limit"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
"github.com/jech/galene/turnserver" "git.stormux.org/storm/skald/turnserver"
"github.com/jech/galene/webserver" "git.stormux.org/storm/skald/webserver"
) )
func main() { func main() {
+59 -59
View File
@@ -1,15 +1,15 @@
# Galene manual # Skald manual
Please see the file [galene-install.md][1] for installation instructions. Please see the file [skald-install.md][1] for installation instructions.
Please see the section *Server administration* below for detailed Please see the section *Server administration* below for detailed
administration instructions. administration instructions.
## Usage ## Usage
Galene includes a web server that, by default, listens on port 8443. Skald includes a web server that, by default, listens on port 8443.
Galene is therefore accessed on URLs such as: Skald is therefore accessed on URLs such as:
https://galene.example.org:8443/ https://skald.example.org:8443/
### The landing page ### The landing page
@@ -87,7 +87,7 @@ commands.
In order to generate an invitation link, choose the entry *Invite user* in In order to generate an invitation link, choose the entry *Invite user* in
the group menu. This generates a link of the form the group menu. This generates a link of the form
https://galene.example.org:8443/group/city-watch/?token=XXX https://skald.example.org:8443/group/city-watch/?token=XXX
where the *XXX* part, known as the *token*, is a shared secret. Such where the *XXX* part, known as the *token*, is a shared secret. Such
a link allows password-less login to the group, and may therefore be a link allows password-less login to the group, and may therefore be
@@ -102,7 +102,7 @@ Tokens can be created, modified, and expired using the `/invite`,
### File transfer ### File transfer
Galene includes a peer-to-peer, end-to-end encrypted file transfer protocol. Skald includes a peer-to-peer, end-to-end encrypted file transfer protocol.
In order to transfer a file, click on the receiver's entry in the user In order to transfer a file, click on the receiver's entry in the user
list and choose *Send file*. list and choose *Send file*.
@@ -131,7 +131,7 @@ file may look as follows:
```json ```json
{ {
"users":{"vetinari": {"password":"lagniappe", "permissions": "admin"}}, "users":{"vetinari": {"password":"lagniappe", "permissions": "admin"}},
"canonicalHost": "galene.example.org", "canonicalHost": "skald.example.org",
"writableGroups": true "writableGroups": true
} }
``` ```
@@ -146,12 +146,12 @@ or, better, with a hashed password:
"permissions": "admin" "permissions": "admin"
} }
}, },
"canonicalHost": "galene.example.org", "canonicalHost": "skald.example.org",
"writableGroups": true "writableGroups": true
} }
``` ```
The file is initially created using `galenectl initial-setup`, but may be The file is initially created using `skaldctl initial-setup`, but may be
manually edited at any time (there is no need to restart the server). The manually edited at any time (there is no need to restart the server). The
fields are as follows: fields are as follows:
@@ -159,7 +159,7 @@ fields are as follows:
same syntax as user definitions in groups (see below), except that the same syntax as user definitions in groups (see below), except that the
only meaningful permission is `"admin"`; only meaningful permission is `"admin"`;
- `writableGroups`: if true, then the API used by `galenectl` can be used - `writableGroups`: if true, then the API used by `skaldctl` can be used
to modify group definitions; if unset or false, then only read-only to modify group definitions; if unset or false, then only read-only
access is allowed; access is allowed;
@@ -167,7 +167,7 @@ fields are as follows:
are allowed to access the server; are allowed to access the server;
- `allowAdminOrigin` is like `allowOrigin`, but applies to the - `allowAdminOrigin` is like `allowOrigin`, but applies to the
administrative API (the one used by `galenectl`); administrative API (the one used by `skaldctl`);
- `proxyURL`: if running behind a reverse proxy, this specifies the root - `proxyURL`: if running behind a reverse proxy, this specifies the root
URL that will be visible outside the proxy; URL that will be visible outside the proxy;
@@ -180,28 +180,28 @@ fields are as follows:
## Group definitions ## Group definitions
Groups are described by JSON files in the `groups/` directory. These Groups are described by JSON files in the `groups/` directory. These
files are normally administered using the `galenectl` utility, but may files are normally administered using the `skaldctl` utility, but may
also be edited manually (there is no need to restart the server). also be edited manually (there is no need to restart the server).
### Managing groups using `galenectl` ### Managing groups using `skaldctl`
#### Creating, modifying, and deleting groups #### Creating, modifying, and deleting groups
A group is created using `galenectl create-group`: A group is created using `skaldctl create-group`:
```sh ```sh
galenectl create-group -group city-watch skaldctl create-group -group city-watch
``` ```
There are a number of options to customise the behaviour of the group, see There are a number of options to customise the behaviour of the group, see
`galenectl create-group -help` for a full list. For example, in order to `skaldctl create-group -help` for a full list. For example, in order to
create a group that allows unrestricted creation of tokens, say: create a group that allows unrestricted creation of tokens, say:
```sh ```sh
galenectl create-group -group city-watch -unrestricted-tokens skaldctl create-group -group city-watch -unrestricted-tokens
``` ```
For more advanced configuration, `galenectl create-group` can be invoked For more advanced configuration, `skaldctl create-group` can be invoked
with the `-json` flag, in which case it takes a JSON template from standard with the `-json` flag, in which case it takes a JSON template from standard
input. The syntax of a JSON template is just like that of a group input. The syntax of a JSON template is just like that of a group
definition file (see below), except that it must not contain the fields definition file (see below), except that it must not contain the fields
@@ -209,35 +209,35 @@ definition file (see below), except that it must not contain the fields
(see the section *Group description reference* below): (see the section *Group description reference* below):
```sh ```sh
echo '{"redirect": "https://galene.example.org:8443/group/city-watch/"}' | galenectl create-group -group amcw -json echo '{"redirect": "https://skald.example.org:8443/group/city-watch/"}' | skaldctl create-group -group amcw -json
``` ```
Groups are modified using `galenectl update-group`: Groups are modified using `skaldctl update-group`:
```sh ```sh
galenectl update-group -group city-watch -unrestricted-tokens=false skaldctl update-group -group city-watch -unrestricted-tokens=false
``` ```
If a JSON template is provided to `galenectl update-group`, then it is If a JSON template is provided to `skaldctl update-group`, then it is
merged with the existing group configuration. Entries may be deleted merged with the existing group configuration. Entries may be deleted
by setting them to `null` in the template: by setting them to `null` in the template:
```sh ```sh
echo '{"redirect": null}' | galenectl update-group -group amcw echo '{"redirect": null}' | skaldctl update-group -group amcw
``` ```
A group is deleted using `galenectl delete-group`: A group is deleted using `skaldctl delete-group`:
```sh ```sh
galenectl delete-group -group amcw skaldctl delete-group -group amcw
``` ```
#### Creating, modifying, and deleting users #### Creating, modifying, and deleting users
A user entry is created with the `galenectl create-user` command: A user entry is created with the `skaldctl create-user` command:
```sh ```sh
galenectl create-user -group city-watch -user vimes -permissions op skaldctl create-user -group city-watch -user vimes -permissions op
``` ```
If the `-permissions` flag is not specified, it defaults to `present`, If the `-permissions` flag is not specified, it defaults to `present`,
@@ -246,14 +246,14 @@ the group. Other useful values are `message`, which allows a user
to participate in the chat only, and `observe`, which doesn't allow any to participate in the chat only, and `observe`, which doesn't allow any
active participation. active participation.
A user is modified using `galenectl update-user`, and deleted using A user is modified using `skaldctl update-user`, and deleted using
`galenectl delete-user`. `skaldctl delete-user`.
In order to be useful, a user entry needs to be assigned a password. This In order to be useful, a user entry needs to be assigned a password. This
is done with the `galenectl set-password` command: is done with the `skaldctl set-password` command:
```sh ```sh
galenectl set-password -group city-watch -user vimes skaldctl set-password -group city-watch -user vimes
``` ```
#### The fallback user #### The fallback user
@@ -262,15 +262,15 @@ It is sometimes useful to allow multiple users to log in using the same
password. This can be achieved by defining the *wildcard* user: password. This can be achieved by defining the *wildcard* user:
```sh ```sh
galenectl create-user -group city-watch -wildcard skaldctl create-user -group city-watch -wildcard
galenectl set-password -group city-watch -wildcard skaldctl set-password -group city-watch -wildcard
``` ```
For open groups, where any user can login with any password, the wildcard For open groups, where any user can login with any password, the wildcard
user's password is set to the password of type `wildcard`: user's password is set to the password of type `wildcard`:
```sh ```sh
galenectl set-password -group city-watch -wildcard -type wildcard skaldctl set-password -group city-watch -wildcard -type wildcard
``` ```
See the section *Client authorisation* below for more information about See the section *Client authorisation* below for more information about
@@ -279,19 +279,19 @@ password types.
#### Automatic subgroups #### Automatic subgroups
It is sometimes necessary to create a large number of identical groups. It is sometimes necessary to create a large number of identical groups.
For example, the author has been using Galene to supervise computer For example, the author has been using Skald to supervise computer
science practicals, where up to 40 students are working in groups of two. science practicals, where up to 40 students are working in groups of two.
While it is possible to automate the creation of groups by accessing While it is possible to automate the creation of groups by accessing
Galene's API, by scripting calls to galenectl, or by generating files Skald's API, by scripting calls to skaldctl, or by generating files
directly under `groups/`, Galene provides a facility called *automatic directly under `groups/`, Skald provides a facility called *automatic
subgroups* that can be used to generate groups on demand. subgroups* that can be used to generate groups on demand.
Automatic subgroups are enabled by setting the `"auto-subgroups"` Automatic subgroups are enabled by setting the `"auto-subgroups"`
field in the group description: field in the group description:
```sh ```sh
galenectl create-group unseen-university -auto-subgroups skaldctl create-group unseen-university -auto-subgroups
``` ```
Whenever a user attempts to access a subgroup of `unseen-university`, for Whenever a user attempts to access a subgroup of `unseen-university`, for
@@ -303,13 +303,13 @@ group's operator can view the list of populated subgroups with the command
#### Managing tokens #### Managing tokens
Tokens are normally managed using the `/invite`, `/reinvite`, and `/expire` Tokens are normally managed using the `/invite`, `/reinvite`, and `/expire`
commands in Galene's user interface, but they may also be managed using commands in Skald's user interface, but they may also be managed using
the `galenectl` utility's `create-token`, `revoke-token`, `delete-token` the `skaldctl` utility's `create-token`, `revoke-token`, `delete-token`
and `list-tokens` commands: and `list-tokens` commands:
```sh ```sh
galenectl create-token -group city-watch skaldctl create-token -group city-watch
galenectl list-tokens -l -group city-watch skaldctl list-tokens -l -group city-watch
``` ```
A token that is generated with the `-include-subgroups` flag applies to A token that is generated with the `-include-subgroups` flag applies to
@@ -317,14 +317,14 @@ the whole hierarchy rooted at the given group, including both ordinary
groups and automatically generated subgroups. groups and automatically generated subgroups.
```sh ```sh
galenectl create-token -group city-watch -include-subgroups skaldctl create-token -group city-watch -include-subgroups
``` ```
Such a token can be attached to the root of the group hierarchy, and Such a token can be attached to the root of the group hierarchy, and
therefore be valid for any group on the server: therefore be valid for any group on the server:
```sh ```sh
galenectl create-token -group '' -include-subgroups skaldctl create-token -group '' -include-subgroups
``` ```
### Group description reference ### Group description reference
@@ -393,7 +393,7 @@ are optional. The following fields are allowed:
A user definition is a dictionary with entries `password` and A user definition is a dictionary with entries `password` and
`permission`. The value of the `password` field is either a plaintext `permission`. The value of the `password` field is either a plaintext
password, or a hashed password generated for example by the `galenectl password, or a hashed password generated for example by the `skaldctl
hash-password` command. The value of the `permissions` field can either hash-password` command. The value of the `permissions` field can either
be an array of individual permissions (not recommended), or one of the be an array of individual permissions (not recommended), or one of the
following strings: following strings:
@@ -425,7 +425,7 @@ anything except Opus.
## Client Authorisation ## Client Authorisation
Galene implements three authorisation methods: a username/password Skald implements three authorisation methods: a username/password
authorisation scheme, a scheme using stateful tokens, and a mechanism authorisation scheme, a scheme using stateful tokens, and a mechanism
based on cryptographic tokens. The former two mechanisms are intended to based on cryptographic tokens. The former two mechanisms are intended to
be used in standalone installations, while the cryptographic mechanism is be used in standalone installations, while the cryptographic mechanism is
@@ -439,13 +439,13 @@ defined directly in the group configuration file, in the `users` and
`wildcard-user` entries. The `users` entry is a dictionary that maps user `wildcard-user` entries. The `users` entry is a dictionary that maps user
names to user descriptions; the `wildcard-user` is a user description names to user descriptions; the `wildcard-user` is a user description
that is used with usernames that don't appear in `users`. These two that is used with usernames that don't appear in `users`. These two
entries are usually managed by the `galenectl` utility. entries are usually managed by the `skaldctl` utility.
Every user description is a dictionary with fields `password` and Every user description is a dictionary with fields `password` and
`permissions`. The `password` field may be a literal password string, or `permissions`. The `password` field may be a literal password string, or
a dictionary describing a hashed password or a wildcard. The a dictionary describing a hashed password or a wildcard. The
`permissions` field should be one of `op`, `present`, `message` or `permissions` field should be one of `op`, `present`, `message` or
`observe`. (An array of Galene's internal permissions is also allowed, `observe`. (An array of Skald's internal permissions is also allowed,
but this is not recommended, since internal permissions may vary from but this is not recommended, since internal permissions may vary from
version to version.) version to version.)
@@ -479,7 +479,7 @@ allows any username with any password.
### Hashed passwords ### Hashed passwords
For security reasons, passwords are usually hashed before being stored in For security reasons, passwords are usually hashed before being stored in
group descriptions (in fact, the `galenectl` utility does not even support group descriptions (in fact, the `skaldctl` utility does not even support
storing plaintext passwords). A hashed password is represented as a JSON storing plaintext passwords). A hashed password is represented as a JSON
dictionary with a field `type` and a number of type-specific fields. dictionary with a field `type` and a number of type-specific fields.
@@ -501,14 +501,14 @@ A user entry with a hashed password looks like this:
``` ```
Hashed passwords are normally generated transparently to the user by the Hashed passwords are normally generated transparently to the user by the
`galenectl set-password` command. When editing group description files `skaldctl set-password` command. When editing group description files
manually, hashed passwords can be generated with the `galenectl hash-password` manually, hashed passwords can be generated with the `skaldctl hash-password`
utility. utility.
### Stateful tokens ### Stateful tokens
Stateful tokens are created by the `/invite` command in the Galene user Stateful tokens are created by the `/invite` command in the Skald user
interface or by the `galenectl create-token` command; see the section interface or by the `skaldctl create-token` command; see the section
*Managing tokens* above. They are stored in the file *Managing tokens* above. They are stored in the file
`data/var/tokens.jsonl`, which, on most filesystems, can be safely backed `data/var/tokens.jsonl`, which, on most filesystems, can be safely backed
up without stopping the server. up without stopping the server.
@@ -516,7 +516,7 @@ up without stopping the server.
### Cryptographic tokens ### Cryptographic tokens
In many cases, it is useful to delegate authorisation decisions to a third In many cases, it is useful to delegate authorisation decisions to a third
party, such as an LDAP or OAuth2 client. Galene implements delegation of party, such as an LDAP or OAuth2 client. Skald implements delegation of
authorisation decisions using cryptographic tokens generated by a third authorisation decisions using cryptographic tokens generated by a third
party known as an *authorisation server*. Two authorisation servers are party known as an *authorisation server*. Two authorisation servers are
available: an [LDAP client][2], and a [sample server written in Python][3]. available: an [LDAP client][2], and a [sample server written in Python][3].
@@ -564,9 +564,9 @@ Alternatively, the group file may specify an authorisation portal using
the `"authPortal"` key. If an authorisation portal is specified, then the the `"authPortal"` key. If an authorisation portal is specified, then the
default client will redirect initial client connections to the default client will redirect initial client connections to the
authorisation portal. The authorisation portal is expected to authorise authorisation portal. The authorisation portal is expected to authorise
the client and then redirect it to Galene with the `username` and `token` the client and then redirect it to Skald with the `username` and `token`
query parameters set. query parameters set.
[1]: <galene-install.md> [1]: <skald-install.md>
[2]: <https://github.com/jech/galene-imap/> [2]: <https://git.stormux.org/storm/skald-imap/>
[3]: <https://github.com/jech/galene-sample-auth-server/> [3]: <https://git.stormux.org/storm/skald-sample-auth-server/>
+38 -38
View File
@@ -26,8 +26,8 @@ import (
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"golang.org/x/term" "golang.org/x/term"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
type configuration struct { type configuration struct {
@@ -55,7 +55,7 @@ var commands = map[string]command{
}, },
"initial-setup": { "initial-setup": {
command: initialSetupCmd, command: initialSetupCmd,
description: "initial setup of Galene and galenectl", description: "initial setup of Skald and skaldctl",
}, },
"set-password": { "set-password": {
command: setPasswordCmd, command: setPasswordCmd,
@@ -125,8 +125,8 @@ func main() {
log.Fatalf("UserConfigDir: %v", err) log.Fatalf("UserConfigDir: %v", err)
} }
configFile = filepath.Join( configFile = filepath.Join(
filepath.Join(configdir, "galene"), filepath.Join(configdir, "skald"),
"galenectl.json", "skaldctl.json",
) )
flag.Usage = func() { flag.Usage = func() {
@@ -332,15 +332,15 @@ func hashPasswordCmd(cmdname string, args []string) {
} }
func initialSetupCmd(cmdname string, args []string) { func initialSetupCmd(cmdname string, args []string) {
var galeneConfigFn string var skaldConfigFn string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, setUsage(cmd, cmdname,
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&galeneConfigFn, "config", "config.json", cmd.StringVar(&skaldConfigFn, "config", "config.json",
"Galene configuration `file`") "Skald configuration `file`")
cmd.Parse(args) cmd.Parse(args)
if cmd.NArg() != 0 { if cmd.NArg() != 0 {
@@ -373,24 +373,24 @@ func initialSetupCmd(cmdname string, args []string) {
} }
} }
galeneConfig, err := os.OpenFile(galeneConfigFn, skaldConfig, err := os.OpenFile(skaldConfigFn,
os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL, os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL,
0600) 0600)
if err != nil { if err != nil {
log.Fatalf("Create %v: %v", galeneConfigFn, err) log.Fatalf("Create %v: %v", skaldConfigFn, err)
} }
galenectlConfig, err := os.OpenFile(configFile, skaldctlConfig, err := os.OpenFile(configFile,
os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL, os.O_WRONLY|os.O_CREATE|os.O_CREATE|os.O_EXCL,
0600) 0600)
if err != nil { if err != nil {
galeneConfig.Close() skaldConfig.Close()
os.Remove(galeneConfigFn) os.Remove(skaldConfigFn)
log.Fatalf("Create %v: %v", galeneConfigFn, err) log.Fatalf("Create %v: %v", skaldConfigFn, err)
} }
defer galeneConfig.Close() defer skaldConfig.Close()
defer galenectlConfig.Close() defer skaldctlConfig.Close()
var users map[string]group.UserDescription var users map[string]group.UserDescription
if adminPassword != "" { if adminPassword != "" {
@@ -416,11 +416,11 @@ func initialSetupCmd(cmdname string, args []string) {
Users: users, Users: users,
} }
encoder := json.NewEncoder(galeneConfig) encoder := json.NewEncoder(skaldConfig)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
err = encoder.Encode(&config) err = encoder.Encode(&config)
if err != nil { if err != nil {
log.Fatalf("Encode %v: %v", galeneConfigFn, err) log.Fatalf("Encode %v: %v", skaldConfigFn, err)
} }
ctlConfig := configuration{ ctlConfig := configuration{
@@ -430,14 +430,14 @@ func initialSetupCmd(cmdname string, args []string) {
AdminToken: adminToken, AdminToken: adminToken,
} }
ctlEncoder := json.NewEncoder(galenectlConfig) ctlEncoder := json.NewEncoder(skaldctlConfig)
ctlEncoder.SetIndent("", " ") ctlEncoder.SetIndent("", " ")
err = ctlEncoder.Encode(&ctlConfig) err = ctlEncoder.Encode(&ctlConfig)
if err != nil { if err != nil {
log.Fatalf("Encode %v: %v", configFile, err) log.Fatalf("Encode %v: %v", configFile, err)
} }
fmt.Printf("The file %v has been created. ", galeneConfigFn) fmt.Printf("The file %v has been created. ", skaldConfigFn)
fmt.Printf("Please copy it into your server's\n\"data/\" directory.\n") fmt.Printf("Please copy it into your server's\n\"data/\" directory.\n")
fmt.Printf("The file %v has been created. ", configFile) fmt.Printf("The file %v has been created. ", configFile)
fmt.Printf("It contains the administrator's\npassword in cleartext, ") fmt.Printf("It contains the administrator's\npassword in cleartext, ")
@@ -655,12 +655,12 @@ func setPasswordCmd(cmdname string, args []string) {
var u string var u string
if wildcard { if wildcard {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user/.password", ".wildcard-user/.password",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".password", ".users", username, ".password",
) )
} }
@@ -710,12 +710,12 @@ func deletePasswordCmd(cmdname string, args []string) {
var err error var err error
if wildcard { if wildcard {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user/.password", ".wildcard-user/.password",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".password", ".users", username, ".password",
) )
} }
@@ -828,7 +828,7 @@ func createGroupCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -874,7 +874,7 @@ func deleteGroupCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -913,7 +913,7 @@ func updateGroupCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1025,7 +1025,7 @@ func listUsersCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/", groupname, u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname,
".users/") ".users/")
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1072,12 +1072,12 @@ func listUsersCmd(cmdname string, args []string) {
func userURL(wildcard bool, groupname, username string) (string, error) { func userURL(wildcard bool, groupname, username string) (string, error) {
if wildcard { if wildcard {
return url.JoinPath( return url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user", ".wildcard-user",
) )
} }
return url.JoinPath( return url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".users", username,
) )
} }
@@ -1250,12 +1250,12 @@ func deleteUserCmd(cmdname string, args []string) {
var err error var err error
if wildcard { if wildcard {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".wildcard-user", ".wildcard-user",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/galene-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.groups", groupname,
".users", username, ".users", username,
) )
} }
@@ -1288,7 +1288,7 @@ func showGroupCmd(cmdname string, args []string) {
log.Fatal("Option \"-group\" is required.") log.Fatal("Option \"-group\" is required.")
} }
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/", groupname) u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname)
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1315,7 +1315,7 @@ func listGroupsCmd(cmdname string, args []string) {
cmd.Parse(args) cmd.Parse(args)
patterns := cmd.Args() patterns := cmd.Args()
u, err := url.JoinPath(serverURL, "/galene-api/v0/.groups/") u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/")
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1365,7 +1365,7 @@ func listTokensCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, ".tokens/", serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
) )
if err != nil { if err != nil {
@@ -1469,7 +1469,7 @@ func createTokenCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, ".tokens/", serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/",
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1505,7 +1505,7 @@ func revokeTokenCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, serverURL, "/skald-api/v0/.groups/", groupname.value,
".tokens", token, ".tokens", token,
) )
if err != nil { if err != nil {
@@ -1544,7 +1544,7 @@ func deleteTokenCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/galene-api/v0/.groups/", groupname.value, serverURL, "/skald-api/v0/.groups/", groupname.value,
".tokens", token, ".tokens", token,
) )
if err != nil { if err != nil {
@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
) )
func TestMakePassword(t *testing.T) { func TestMakePassword(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css"> <link rel="stylesheet" href="/common.css">
<link rel="stylesheet" href="/change-password.css"> <link rel="stylesheet" href="/change-password.css">
<link rel="stylesheet" type="text/css" href="/galene.css"/> <link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/> <link rel="author" href="https://www.irif.fr/~jch/"/>
</head> </head>
+1 -1
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Galène client example</title> <title>Skald client example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="author" href="https://www.irif.fr/~jch/"/> <link rel="author" href="https://www.irif.fr/~jch/"/>
</head> </head>
+1 -1
View File
@@ -1,4 +1,4 @@
/* Galene client example. */ /* Skald client example. */
/** /**
* The main function. * The main function.
+4 -4
View File
@@ -1,19 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Galène</title> <title>Skald</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css"> <link rel="stylesheet" href="/common.css">
<link rel="stylesheet" href="/mainpage.css"> <link rel="stylesheet" href="/mainpage.css">
<link rel="stylesheet" type="text/css" href="/galene.css"/> <link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/> <link rel="author" href="https://www.irif.fr/~jch/"/>
</head> </head>
<body> <body>
<div class="home"> <div class="home">
<h1 id="title" class="navbar-brand">Galène</h1> <h1 id="title" class="navbar-brand">Skald</h1>
<form id="groupform"> <form id="groupform">
<label for="group">Group:</label> <label for="group">Group:</label>
@@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<footer class="signature"> <footer class="signature">
<p><a href="https://galene.org/">Galène</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p> <p><a href="https://skald.org/">Skald</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p>
</footer> </footer>
<script src="/mainpage.js" defer></script> <script src="/mainpage.js" defer></script>
+13 -13
View File
@@ -159,7 +159,7 @@ async function updateObject(url, values, etag) {
* @returns {Promise<Array<string>>} * @returns {Promise<Array<string>>}
*/ */
async function listGroups() { async function listGroups() {
return await listObjects('/galene-api/v0/.groups/'); return await listObjects('/skald-api/v0/.groups/');
} }
/** /**
@@ -170,7 +170,7 @@ async function listGroups() {
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
async function getGroup(group, etag) { async function getGroup(group, etag) {
return await getObject(`/galene-api/v0/.groups/${group}`, etag); return await getObject(`/skald-api/v0/.groups/${group}`, etag);
} }
/** /**
@@ -180,7 +180,7 @@ async function getGroup(group, etag) {
* @param {Object} [values] * @param {Object} [values]
*/ */
async function createGroup(group, values) { async function createGroup(group, values) {
return await createObject(`/galene-api/v0/.groups/${group}`, values); return await createObject(`/skald-api/v0/.groups/${group}`, values);
} }
/** /**
@@ -190,7 +190,7 @@ async function createGroup(group, values) {
* @param {string} [etag] * @param {string} [etag]
*/ */
async function deleteGroup(group, etag) { async function deleteGroup(group, etag) {
return await deleteObject(`/galene-api/v0/.groups/${group}`, etag); return await deleteObject(`/skald-api/v0/.groups/${group}`, etag);
} }
/** /**
@@ -203,7 +203,7 @@ async function deleteGroup(group, etag) {
* @param {string} [etag] * @param {string} [etag]
*/ */
async function updateGroup(group, values, etag) { async function updateGroup(group, values, etag) {
return await updateObject(`/galene-api/v0/.groups/${group}`, values); return await updateObject(`/skald-api/v0/.groups/${group}`, values);
} }
/** /**
@@ -213,7 +213,7 @@ async function updateGroup(group, values, etag) {
* @returns {Promise<Array<string>>} * @returns {Promise<Array<string>>}
*/ */
async function listUsers(group) { async function listUsers(group) {
return await listObjects(`/galene-api/v0/.groups/${group}/.users/`); return await listObjects(`/skald-api/v0/.groups/${group}/.users/`);
} }
/** /**
@@ -225,11 +225,11 @@ async function listUsers(group) {
*/ */
function userURL(group, user, wildcard) { function userURL(group, user, wildcard) {
if(wildcard) if(wildcard)
return `/galene-api/v0/.groups/${group}/.wildcard-user`; return `/skald-api/v0/.groups/${group}/.wildcard-user`;
else if(user === "") else if(user === "")
return `/galene-api/v0/.groups/${group}/.empty-user`; return `/skald-api/v0/.groups/${group}/.empty-user`;
else else
return `/galene-api/v0/.groups/${group}/.users/${user}` return `/skald-api/v0/.groups/${group}/.users/${user}`
} }
/** /**
@@ -320,7 +320,7 @@ async function setPassword(group, user, wildcard, password, oldpassword) {
* @returns {Promise<Array<string>>} * @returns {Promise<Array<string>>}
*/ */
async function listTokens(group) { async function listTokens(group) {
return await listObjects(`/galene-api/v0/.groups/${group}/.tokens/`); return await listObjects(`/skald-api/v0/.groups/${group}/.tokens/`);
} }
/** /**
@@ -332,7 +332,7 @@ async function listTokens(group) {
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
async function getToken(group, token, etag) { async function getToken(group, token, etag) {
return await getObject(`/galene-api/v0/.groups/${group}/.tokens/${token}`, return await getObject(`/skald-api/v0/.groups/${group}/.tokens/${token}`,
etag); etag);
} }
@@ -353,7 +353,7 @@ async function createToken(group, template) {
} }
let r = await fetch( let r = await fetch(
`/galene-api/v0/.groups/${group}/.tokens/`, `/skald-api/v0/.groups/${group}/.tokens/`,
options); options);
if(!r.ok) if(!r.ok)
throw httpError(r); throw httpError(r);
@@ -373,6 +373,6 @@ async function updateToken(group, token, etag) {
if(!token.token) if(!token.token)
throw new Error("Unnamed token"); throw new Error("Unnamed token");
return await updateObject( return await updateObject(
`/galene-api/v0/.groups/${group}/.tokens/${token.token}`, `/skald-api/v0/.groups/${group}/.tokens/${token.token}`,
token, etag); token, etag);
} }
+1 -1
View File
@@ -1619,7 +1619,7 @@ function TransferredFile(sc, userid, id, up, username, name, mimetype, size) {
* The negotiated file-transfer protocol version. * The negotiated file-transfer protocol version.
* *
* This is the version of the file-transfer protocol, and is not * This is the version of the file-transfer protocol, and is not
* necessarily equal to the version of the Galene protocol used by the * necessarily equal to the version of the Skald protocol used by the
* server connection. * server connection.
* *
* @type {string} * @type {string}
+2 -2
View File
@@ -1109,7 +1109,7 @@ legend {
border-right: 1px solid #dcdcdc; border-right: 1px solid #dcdcdc;
} }
#left-sidebar .galene-header { #left-sidebar .skald-header {
display: inline-block; display: inline-block;
} }
@@ -1127,7 +1127,7 @@ header .collapse:hover {
color: #c2a4e0; color: #c2a4e0;
} }
.galene-header { .skald-header {
font-size: 1.3rem; font-size: 1.3rem;
font-weight: 900; font-weight: 900;
color: #dbd9d9; color: #dbd9d9;
+5 -5
View File
@@ -1,12 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Galène</title> <title>Skald</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="ScreenOrientation" content="autoRotate:disabled"> <meta http-equiv="ScreenOrientation" content="autoRotate:disabled">
<link rel="stylesheet" type="text/css" href="/common.css"/> <link rel="stylesheet" type="text/css" href="/common.css"/>
<link rel="stylesheet" type="text/css" href="/galene.css"/> <link rel="stylesheet" type="text/css" href="/skald.css"/>
<link rel="author" href="https://www.irif.fr/~jch/"/> <link rel="author" href="https://www.irif.fr/~jch/"/>
<link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/fontawesome.min.css"/> <link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/fontawesome.min.css"/>
<link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/solid.min.css"/> <link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/solid.min.css"/>
@@ -20,7 +20,7 @@
<div class="row full-height"> <div class="row full-height">
<nav id="left-sidebar"> <nav id="left-sidebar">
<div class="users-header"> <div class="users-header">
<div class="galene-header">Galène</div> <div class="skald-header">Skald</div>
</div> </div>
<div class="header-sep"></div> <div class="header-sep"></div>
<div id="users"></div> <div id="users"></div>
@@ -32,7 +32,7 @@
<button class="collapse" aria-label="Toggle left panel" aria-expanded="true" id="sidebarCollapse"> <button class="collapse" aria-label="Toggle left panel" aria-expanded="true" id="sidebarCollapse">
<i class="fas fa-align-left" aria-hidden="true"></i> <i class="fas fa-align-left" aria-hidden="true"></i>
</button> </button>
<h1 id="title" class="header-title">Galène</h1> <h1 id="title" class="header-title">Skald</h1>
</div> </div>
<ul class="nav-menu"> <ul class="nav-menu">
@@ -312,6 +312,6 @@
<script src="/protocol.js" defer></script> <script src="/protocol.js" defer></script>
<script src="/third-party/toastify/toastify.js" defer></script> <script src="/third-party/toastify/toastify.js" defer></script>
<script src="/third-party/contextual/contextual.js" defer></script> <script src="/third-party/contextual/contextual.js" defer></script>
<script src="/galene.js" defer></script> <script src="/skald.js" defer></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -2869,7 +2869,7 @@ function setTitle(title) {
if(title) if(title)
set(title); set(title);
else else
set('Galène'); set('Skald');
} }
/** /**
@@ -3245,7 +3245,7 @@ function gotUserMessage(id, dest, username, time, privileged, kind, error, messa
if('share' in navigator) { if('share' in navigator) {
try { try {
navigator.share({ navigator.share({
title: `Invitation to Galene group ${message.group}`, title: `Invitation to Skald group ${message.group}`,
text: f[0], text: f[0],
url: f[1], url: f[1],
}); });
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Galène statistics</title> <title>Skald statistics</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/common.css"> <link rel="stylesheet" href="/common.css">
@@ -10,7 +10,7 @@
<body> <body>
<h1 id="title" class="navbar-brand">Galène statistics</h1> <h1 id="title" class="navbar-brand">Skald statistics</h1>
<table id="stats-table"></table> <table id="stats-table"></table>
<script src="/stats.js" defer></script> <script src="/stats.js" defer></script>
</body> </body>
+1 -1
View File
@@ -25,7 +25,7 @@ async function listStats() {
let l; let l;
try { try {
let r = await fetch('/galene-api/v0/.stats'); let r = await fetch('/skald-api/v0/.stats');
if(!r.ok) if(!r.ok)
throw new Error(`${r.status} ${r.statusText}`); throw new Error(`${r.status} ${r.statusText}`);
l = await r.json(); l = await r.json();
+1 -1
View File
@@ -15,7 +15,7 @@
}, },
"files": [ "files": [
"protocol.js", "protocol.js",
"galene.js", "skald.js",
"management.js", "management.js",
"example/example.js" "example/example.js"
] ]
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"sort" "sort"
"time" "time"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
) )
type GroupStats struct { type GroupStats struct {
+26 -26
View File
@@ -26,12 +26,12 @@
# PHASE 0: BASELINE INVENTORY AND GUARDRAILS # PHASE 0: BASELINE INVENTORY AND GUARDRAILS
# ============================================================================ # ============================================================================
- Record the current upstream/fork point in CHANGES or a new fork note X Record the current upstream/fork point in CHANGES or a new fork note
- Capture current baseline: `go test ./...` result before behavior changes X Capture current baseline: `go test ./...` result before behavior changes
- Capture current baseline: `go build ./...` result before behavior changes X Capture current baseline: `go build ./...` result before behavior changes
- Inventory generated/static assets, including static/example and third-party assets - Inventory generated/static assets, including static/example and third-party assets
- Inventory all public URLs: /group/, /public-groups.json, /recordings/, /galene-api/ - Inventory all public URLs: /group/, /public-groups.json, /recordings/, /galene-api/
- Inventory all config/data paths: groups/, data/, recordings/, galenectl.json - Inventory all config/data paths: groups/, data/, recordings/, skaldctl.json
- Inventory all Galene binary/service names in docs, scripts, examples, and tests - Inventory all Galene binary/service names in docs, scripts, examples, and tests
- Decide whether existing stored data will be migrated manually or treated as incompatible - Decide whether existing stored data will be migrated manually or treated as incompatible
- Keep the old LICENSE intact and preserve required attribution - Keep the old LICENSE intact and preserve required attribution
@@ -40,23 +40,23 @@
# PHASE 1: FOUNDATION AND TOTAL REBRANDING # PHASE 1: FOUNDATION AND TOTAL REBRANDING
# ============================================================================ # ============================================================================
- Rename Go module: github.com/jech/galene -> git.stormux.org/storm/skald X Rename Go module: github.com/jech/galene -> git.stormux.org/storm/skald
- Update all internal Go import paths across source and tests X Update all internal Go import paths across source and tests
- Rename galene.go -> skald.go and keep the main package entrypoint working X Rename galene.go -> skald.go and keep the main package entrypoint working
- Rename galenectl/ directory and binary to skaldctl/ X Rename galenectl/ directory and binary to skaldctl/
- Rename static/galene.css -> static/skald.css X Rename static/galene.css -> static/skald.css
- Rename static/galene.js -> static/skald.js X Rename static/galene.js -> static/skald.js
- Rename static/galene.html -> static/skald.html X Rename static/galene.html -> static/skald.html
- Update webserver static serving to load skald.html and renamed assets X Update webserver static serving to load skald.html and renamed assets
- Update all HTML, JS, CSS, docs, tests, and examples that reference renamed assets X Update all HTML, JS, CSS, docs, tests, and examples that reference renamed assets
- Rename galene-*.md docs to skald-*.md X Rename galene-*.md docs to skald-*.md
- Replace "Galene", "Galène", and "galene" with "Skald" and "skald" where they refer to this project X Replace "Galene", "Galène", and "galene" with "Skald" and "skald" where they refer to this project
- Keep historical upstream references only where they are attribution, changelog history, or license context X Keep historical upstream references only where they are attribution, changelog history, or license context
- Rename default binary, service, and install examples from galene to skald X Rename default binary, service, and install examples from galene to skald
- Rename galenectl config file to skaldctl.json X Rename galenectl config file to skaldctl.json
- Update CHANGES with Skald fork point and first Skald development section X Update CHANGES with Skald fork point and first Skald development section
- Verify `go test ./...` compiles after the pure project rename pass X Verify `go test ./...` compiles after the pure project rename pass
- Search for remaining Galene names and classify each survivor as attribution/history or a bug X Search for remaining Galene names and classify each survivor as attribution/history or a bug
# ============================================================================ # ============================================================================
# PHASE 2: HALL TERMINOLOGY AND DATA MODEL # PHASE 2: HALL TERMINOLOGY AND DATA MODEL
@@ -92,13 +92,13 @@
- Update static/protocol.js public API: ServerConnection.join and related callbacks - Update static/protocol.js public API: ServerConnection.join and related callbacks
- Update static/management.js to use /skald-api/v0/.halls/ - Update static/management.js to use /skald-api/v0/.halls/
- Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json - Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json
- Update stats endpoint naming only if it exposes Galene/group terminology - Update stats endpoint naming only if it exposes Skald/group terminology
- Update skaldctl command names: create-hall, update-hall, delete-hall, list-halls - Update skaldctl command names: create-hall, update-hall, delete-hall, list-halls
- Update skaldctl flags: -hall, -include-subhalls, -auto-subhalls, etc. - Update skaldctl flags: -hall, -include-subhalls, -auto-subhalls, etc.
- Update skaldctl help text and error messages - Update skaldctl help text and error messages
- Update API and webserver tests for /hall/ and /skald-api/ - Update API and webserver tests for /hall/ and /skald-api/
- Verify old /group/, /public-groups.json, /galene-api/, and .groups paths fail clearly - Verify old /group/, /public-groups.json, /galene-api/, and .groups paths fail clearly
- Verify protocol backward compatibility with Galene is intentionally broken - Verify protocol backward compatibility with Skald is intentionally broken
# ============================================================================ # ============================================================================
# PHASE 4: BACKEND - STRIP VIDEO COMPLETELY # PHASE 4: BACKEND - STRIP VIDEO COMPLETELY
@@ -189,7 +189,7 @@
- Update TURN/STUN docs only where project name or URLs changed - Update TURN/STUN docs only where project name or URLs changed
- Update Docker/container or packaging files if present later - Update Docker/container or packaging files if present later
- Update .gitignore for renamed binaries, config files, and generated artifacts - Update .gitignore for renamed binaries, config files, and generated artifacts
- Verify there are no stale install commands that build or run galene/galenectl - Verify there are no stale install commands that build or run skald/skaldctl
# ============================================================================ # ============================================================================
# PHASE 8: DOCUMENTATION # PHASE 8: DOCUMENTATION
@@ -204,7 +204,7 @@
- Remove video, camera, screenshare, simulcast, SVC, and WebM recording instructions - Remove video, camera, screenshare, simulcast, SVC, and WebM recording instructions
- Add Ogg Opus recording documentation - Add Ogg Opus recording documentation
- Document that incoming video is rejected by design - Document that incoming video is rejected by design
- Document that Galene protocol/API compatibility is intentionally broken - Document that Skald protocol/API compatibility is intentionally broken
- Document any required external ffmpeg dependency if the recorder uses ffmpeg - Document any required external ffmpeg dependency if the recorder uses ffmpeg
- Check docs for old real-person sample usernames and replace with placeholders - Check docs for old real-person sample usernames and replace with placeholders
@@ -235,4 +235,4 @@
- Test: play the recording in a standard audio player - Test: play the recording in a standard audio player
- Test: restart server and verify halls/config load from the new paths - Test: restart server and verify halls/config load from the new paths
- Remove obsolete files that are no longer served or built - Remove obsolete files that are no longer served or built
- Do a final diff review for accidental compatibility shims or stale Galene naming - Do a final diff review for accidental compatibility shims or stale Skald naming
+64 -12
View File
@@ -6,6 +6,8 @@ import (
"encoding/json" "encoding/json"
"reflect" "reflect"
"testing" "testing"
"github.com/golang-jwt/jwt/v5"
) )
func TestJWKHS256(t *testing.T) { func TestJWKHS256(t *testing.T) {
@@ -113,6 +115,17 @@ func TestMatchGroup(t *testing.T) {
} }
} }
func makeTestJWT(t *testing.T, key any, claims jwt.MapClaims) string {
t.Helper()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
value, err := token.SignedString(key)
if err != nil {
t.Fatalf("SignedString: %v", err)
}
return value
}
func TestJWT(t *testing.T) { func TestJWT(t *testing.T) {
key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}` key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}`
var k map[string]interface{} var k map[string]interface{}
@@ -122,17 +135,35 @@ func TestJWT(t *testing.T) {
} }
keys := []map[string]interface{}{k} keys := []map[string]interface{}{k}
signingKey, err := ParseKey(k)
if err != nil {
t.Fatalf("ParseKey: %v", err)
}
john := "john" john := "john"
jack := "jack" jack := "jack"
baseClaims := jwt.MapClaims{
"aud": "https://skald.org:8443/group/auth/",
"permissions": []string{"present"},
"iat": int64(1645310294),
"exp": int64(2906750294),
"iss": "http://localhost:1234/",
}
goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDI5NCwiZXhwIjoyOTA2NzUwMjk0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.6xXpgBkBMn4PSBpnwYHb-gRn_Q97Yq9DoKkAf2_6iwc" goodToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "john",
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err := Parse(goodToken, keys) tok, err := Parse(goodToken, keys)
if err != nil { if err != nil {
t.Errorf("Couldn't parse goodToken: %v", err) t.Errorf("Couldn't parse goodToken: %v", err)
} }
username, perms, err := tok.Check("galene.org:8443", "auth", &john) username, perms, err := tok.Check("skald.org:8443", "auth", &john)
if err != nil { if err != nil {
t.Errorf("goodToken is not valid: %v", err) t.Errorf("goodToken is not valid: %v", err)
} }
@@ -140,7 +171,7 @@ func TestJWT(t *testing.T) {
t.Errorf("Expected john, [present], got %v %v", username, perms) t.Errorf("Expected john, [present], got %v %v", username, perms)
} }
username, perms, err = tok.Check("galene.org:8443", "auth", &jack) username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil { if err != nil {
t.Errorf("goodToken is not valid: %v", err) t.Errorf("goodToken is not valid: %v", err)
} }
@@ -153,28 +184,35 @@ func TestJWT(t *testing.T) {
t.Errorf("goodToken is not valid: %v", err) t.Errorf("goodToken is not valid: %v", err)
} }
_, _, err = tok.Check("galene.org", "auth", &john) _, _, err = tok.Check("skald.org", "auth", &john)
if err == nil { if err == nil {
t.Errorf("goodToken is valid for wrong hostname") t.Errorf("goodToken is valid for wrong hostname")
} }
_, _, err = tok.Check("galene.org:8443", "not-auth", &john) _, _, err = tok.Check("skald.org:8443", "not-auth", &john)
if err == nil { if err == nil {
t.Errorf("goodToken is valid for wrong group") t.Errorf("goodToken is valid for wrong group")
} }
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john) _, _, err = tok.Check("skald.org:8443", "auth/subgroup", &john)
if err == nil { if err == nil {
t.Errorf("goodToken is valid for subgroup") t.Errorf("goodToken is valid for subgroup")
} }
emptySubToken := "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIiLCJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.xwpHIRzKAIgiHKG1pVQyZlXcolmvRwNvBm6FN2gTwZw" emptySubToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "",
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(emptySubToken, keys) tok, err = Parse(emptySubToken, keys)
if err != nil { if err != nil {
t.Errorf("Couldn't parse emptySubToken: %v", err) t.Errorf("Couldn't parse emptySubToken: %v", err)
} }
username, perms, err = tok.Check("galene.org:8443", "auth", &jack) username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil { if err != nil {
t.Errorf("anonymousToken is not valid: %v", err) t.Errorf("anonymousToken is not valid: %v", err)
} }
@@ -182,13 +220,19 @@ func TestJWT(t *testing.T) {
t.Errorf("Expected \"\", [present], got %v %v", username, perms) t.Errorf("Expected \"\", [present], got %v %v", username, perms)
} }
noSubToken := "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwczovL2dhbGVuZS5vcmc6ODQ0My9ncm91cC9hdXRoLyIsInBlcm1pc3Npb25zIjpbInByZXNlbnQiXSwiaWF0IjoxNjQ1MzEwMjk0LCJleHAiOjI5MDY3NTAyOTQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQo.7LvoZEKPNVvsRe8SjLxmKa1TgjTA4ZQo2LMPJSXl-ro" noSubToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"aud": baseClaims["aud"],
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(noSubToken, keys) tok, err = Parse(noSubToken, keys)
if err != nil { if err != nil {
t.Errorf("Couldn't parse noSubToken: %v", err) t.Errorf("Couldn't parse noSubToken: %v", err)
} }
username, perms, err = tok.Check("galene.org:8443", "auth", &jack) username, perms, err = tok.Check("skald.org:8443", "auth", &jack)
if err != nil { if err != nil {
t.Errorf("noSubToken is not valid: %v", err) t.Errorf("noSubToken is not valid: %v", err)
} }
@@ -216,13 +260,21 @@ func TestJWT(t *testing.T) {
t.Errorf("noneToken is good") t.Errorf("noneToken is good")
} }
subgroupsToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJpbmNsdWRlLXN1Ymdyb3VwcyI6dHJ1ZSwicGVybWlzc2lvbnMiOlsicHJlc2VudCJdLCJpYXQiOjE2NDUzMTAyOTQsImV4cCI6MjkwNjc1MDI5NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDoxMjM0LyJ9.ty4B7cJPCGJPMadwOCNMGQirIByj1L9AxWiDFnBm5wA" subgroupsToken := makeTestJWT(t, signingKey, jwt.MapClaims{
"sub": "john",
"aud": baseClaims["aud"],
"include-subgroups": true,
"permissions": baseClaims["permissions"],
"iat": baseClaims["iat"],
"exp": baseClaims["exp"],
"iss": baseClaims["iss"],
})
tok, err = Parse(subgroupsToken, keys) tok, err = Parse(subgroupsToken, keys)
if err != nil { if err != nil {
t.Errorf("subgroupsToken is not valid: %v", err) t.Errorf("subgroupsToken is not valid: %v", err)
} }
_, _, err = tok.Check("galene.org:8443", "auth/subgroup", &john) _, _, err = tok.Check("skald.org:8443", "auth/subgroup", &john)
if err != nil { if err != nil {
t.Errorf("subgroupsToken is not valid for subgroup: %v", err) t.Errorf("subgroupsToken is not valid for subgroup: %v", err)
} }
+1 -1
View File
@@ -184,7 +184,7 @@ func (state *state) load() (string, error) {
state.reset() state.reset()
return "", err return "", err
} }
// the "message" permission was introduced in Galene 0.9, // the "message" permission was introduced in Skald 0.9,
// so add it to tokens read from disk. We can remove this // so add it to tokens read from disk. We can remove this
// hack in late 2024. // hack in late 2024.
if !member("message", t.Permissions) { if !member("message", t.Permissions) {
+1 -1
View File
@@ -39,7 +39,7 @@ func TestToken(t *testing.T) {
t.Fatalf("Parse: %v", err) t.Fatalf("Parse: %v", err)
} }
_, _, err = token.Check("galene.org:8443", "group", &user) _, _, err = token.Check("skald.org:8443", "group", &user)
if err != nil { if err != nil {
t.Errorf("Check: %v", err) t.Errorf("Check: %v", err)
} }
+3 -3
View File
@@ -113,7 +113,7 @@ func Start() error {
return err return err
} }
username = "galene" username = "skald"
buf := make([]byte, 6) buf := make([]byte, 6)
_, err = rand.Read(buf) _, err = rand.Read(buf)
if err != nil { if err != nil {
@@ -187,9 +187,9 @@ func Start() error {
log.Printf("Starting built-in TURN server on %v", addr.String()) log.Printf("Starting built-in TURN server on %v", addr.String())
server.server, err = turn.NewServer(turn.ServerConfig{ server.server, err = turn.NewServer(turn.ServerConfig{
Realm: "galene.org", Realm: "skald.org",
AuthHandler: func(u, r string, src net.Addr) ([]byte, bool) { AuthHandler: func(u, r string, src net.Addr) ([]byte, bool) {
if u != username || r != "galene.org" { if u != username || r != "skald.org" {
return nil, false return nil, false
} }
return turn.GenerateAuthKey(u, r, password), true return turn.GenerateAuthKey(u, r, password), true
+7 -7
View File
@@ -13,9 +13,9 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/stats" "git.stormux.org/storm/skald/stats"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
// checkAdmin checks whether the client authentifies as an administrator // checkAdmin checks whether the client authentifies as an administrator
@@ -25,7 +25,7 @@ func checkAdmin(w http.ResponseWriter, r *http.Request) bool {
ok, _ = adminMatch(username, password) ok, _ = adminMatch(username, password)
} }
if !ok { if !ok {
failAuthentication(w, "/galene-api/") failAuthentication(w, "/skald-api/")
return false return false
} }
return true return true
@@ -71,7 +71,7 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user
} }
} }
} }
failAuthentication(w, "/galene-api/") failAuthentication(w, "/skald-api/")
return false return false
} }
@@ -141,12 +141,12 @@ func apiCORS(w http.ResponseWriter, r *http.Request, methods string) bool {
} }
func apiHandler(w http.ResponseWriter, r *http.Request) { func apiHandler(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.URL.Path, "/galene-api/") { if !strings.HasPrefix(r.URL.Path, "/skald-api/") {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
first, kind, rest := splitPath(r.URL.Path[len("/galene-api"):]) first, kind, rest := splitPath(r.URL.Path[len("/skald-api"):])
if first != "/v0" { if first != "/v0" {
http.NotFound(w, r) http.NotFound(w, r)
return return
+52 -52
View File
@@ -15,8 +15,8 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/token" "git.stormux.org/storm/skald/token"
) )
var setupOnce sync.Once var setupOnce sync.Once
@@ -115,19 +115,19 @@ func TestApi(t *testing.T) {
} }
var groups []string var groups []string
err = getJSON("/galene-api/v0/.groups/", &groups) err = getJSON("/skald-api/v0/.groups/", &groups)
if err != nil || len(groups) != 0 { if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err) t.Errorf("Get groups: %v", err)
} }
resp, err := do("PUT", "/galene-api/v0/.groups/test/", resp, err := do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "\"foo\"", "", "application/json", "\"foo\"", "",
"{}") "{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed { if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode) t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"text/plain", "", "", "text/plain", "", "",
"Hello, world!") "Hello, world!")
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
@@ -135,7 +135,7 @@ func TestApi(t *testing.T) {
err, resp.StatusCode) err, resp.StatusCode)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "", "*", "application/json", "", "*",
"{}") "{}")
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
@@ -143,30 +143,30 @@ func TestApi(t *testing.T) {
} }
var desc *group.Description var desc *group.Description
err = getJSON("/galene-api/v0/.groups/test/", &desc) err = getJSON("/skald-api/v0/.groups/test/", &desc)
if err != nil || len(desc.Users) != 0 { if err != nil || len(desc.Users) != 0 {
t.Errorf("Get group: %v", err) t.Errorf("Get group: %v", err)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.groups/test/",
"application/json", "", "*", "application/json", "", "*",
"{}") "{}")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed { if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode) t.Errorf("Create group (bad ETag): %v %v", err, resp.StatusCode)
} }
resp, err = do("DELETE", "/galene-api/v0/.groups/test/", resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
"", "", "*", "") "", "", "*", "")
if err != nil || resp.StatusCode != http.StatusPreconditionFailed { if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode) t.Errorf("Delete group (bad ETag): %v %v", err, resp.StatusCode)
} }
err = getJSON("/galene-api/v0/.groups/", &groups) err = getJSON("/skald-api/v0/.groups/", &groups)
if err != nil || len(groups) != 1 || groups[0] != "test" { if err != nil || len(groups) != 1 || groups[0] != "test" {
t.Errorf("Get groups: %v %v", err, groups) t.Errorf("Get groups: %v %v", err, groups)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.keys", resp, err = do("PUT", "/skald-api/v0/.groups/test/.keys",
"application/jwk-set+json", "", "", "application/jwk-set+json", "", "",
`{"keys": [{ `{"keys": [{
"kty": "oct", "alg": "HS256", "kty": "oct", "alg": "HS256",
@@ -176,12 +176,12 @@ func TestApi(t *testing.T) {
t.Errorf("Set key: %v %v", err, resp.StatusCode) t.Errorf("Set key: %v %v", err, resp.StatusCode)
} }
err = getJSON("/galene-api/v0/.groups/test/.users/", &groups) err = getJSON("/skald-api/v0/.groups/test/.users/", &groups)
if err != nil || len(groups) != 0 { if err != nil || len(groups) != 0 {
t.Errorf("Get users: %v", err) t.Errorf("Get users: %v", err)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"text/plain", "", "*", "text/plain", "", "*",
`hello, world!`) `hello, world!`)
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
@@ -189,7 +189,7 @@ func TestApi(t *testing.T) {
err, resp.StatusCode) err, resp.StatusCode)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"application/json", "", "*", "application/json", "", "*",
`{"permissions": "present"}`) `{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
@@ -197,26 +197,26 @@ func TestApi(t *testing.T) {
} }
var users []string var users []string
err = getJSON("/galene-api/v0/.groups/test/.users/", &users) err = getJSON("/skald-api/v0/.groups/test/.users/", &users)
if err != nil || len(users) != 1 || users[0] != "jch" { if err != nil || len(users) != 1 || users[0] != "jch" {
t.Errorf("Get users: %v %v", err, users) t.Errorf("Get users: %v %v", err, users)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch",
"application/json", "", "*", "application/json", "", "*",
`{"permissions": "present"}`) `{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusPreconditionFailed { if err != nil || resp.StatusCode != http.StatusPreconditionFailed {
t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode) t.Errorf("Create user (bad ETag): %v %v", err, resp.StatusCode)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password", resp, err = do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password",
"application/json", "", "", "application/json", "", "",
`"toto"`) `"toto"`)
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode) t.Errorf("Set password (PUT): %v %v", err, resp.StatusCode)
} }
resp, err = do("POST", "/galene-api/v0/.groups/test/.users/jch/.password", resp, err = do("POST", "/skald-api/v0/.groups/test/.users/jch/.password",
"text/plain", "", "", "text/plain", "", "",
`toto`) `toto`)
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
@@ -224,7 +224,7 @@ func TestApi(t *testing.T) {
} }
var user group.UserDescription var user group.UserDescription
err = getJSON("/galene-api/v0/.groups/test/.users/jch", &user) err = getJSON("/skald-api/v0/.groups/test/.users/jch", &user)
if err != nil { if err != nil {
t.Errorf("Get user: %v", err) t.Errorf("Get user: %v", err)
} }
@@ -245,7 +245,7 @@ func TestApi(t *testing.T) {
t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type) t.Errorf("Password.Type: %v", desc.Users["jch"].Password.Type)
} }
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.users/jch", resp, err = do("DELETE", "/skald-api/v0/.groups/test/.users/jch",
"", "", "", "") "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode) t.Errorf("Delete group: %v %v", err, resp.StatusCode)
@@ -260,14 +260,14 @@ func TestApi(t *testing.T) {
t.Errorf("Users (after delete): %#v", desc.Users) t.Errorf("Users (after delete): %#v", desc.Users)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test/.wildcard-user", resp, err = do("PUT", "/skald-api/v0/.groups/test/.wildcard-user",
"application/json", "", "*", "application/json", "", "*",
`{"permissions": "present"}`) `{"permissions": "present"}`)
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create wildcard user: %v %v", err, resp.StatusCode) t.Errorf("Create wildcard user: %v %v", err, resp.StatusCode)
} }
err = getJSON("/galene-api/v0/.groups/test/.wildcard-user", &user) err = getJSON("/skald-api/v0/.groups/test/.wildcard-user", &user)
if err != nil { if err != nil {
t.Errorf("Get wildcard user: %v", err) t.Errorf("Get wildcard user: %v", err)
} }
@@ -281,7 +281,7 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v", desc.WildcardUser, user) t.Errorf("Got %v, expected %v", desc.WildcardUser, user)
} }
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.wildcard-user", resp, err = do("DELETE", "/skald-api/v0/.groups/test/.wildcard-user",
"", "", "", "") "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode) t.Errorf("Delete wildcard user: %v %v", err, resp.StatusCode)
@@ -300,33 +300,33 @@ func TestApi(t *testing.T) {
t.Errorf("Keys: %v", len(desc.AuthKeys)) t.Errorf("Keys: %v", len(desc.AuthKeys))
} }
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/", resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
"application/json", "", "", `{"group":"bad"}`) "application/json", "", "", `{"group":"bad"}`)
if err != nil || resp.StatusCode != http.StatusBadRequest { if err != nil || resp.StatusCode != http.StatusBadRequest {
t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode) t.Errorf("Create token (bad group): %v %v", err, resp.StatusCode)
} }
resp, err = do("POST", "/galene-api/v0/.groups/test/.tokens/", resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/",
"application/json", "", "", "{}") "application/json", "", "", "{}")
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create token: %v %v", err, resp.StatusCode) t.Errorf("Create token: %v %v", err, resp.StatusCode)
} }
resp, err = do("POST", "/galene-api/v0/.groups/.tokens/", resp, err = do("POST", "/skald-api/v0/.groups/.tokens/",
"application/json", "", "", "{\"includeSubgroups\": true}") "application/json", "", "", "{\"includeSubgroups\": true}")
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create global token: %v %v", err, resp.StatusCode) t.Errorf("Create global token: %v %v", err, resp.StatusCode)
} }
var toknames []string var toknames []string
err = getJSON("/galene-api/v0/.groups/test/.tokens/", &toknames) err = getJSON("/skald-api/v0/.groups/test/.tokens/", &toknames)
if err != nil || len(toknames) != 1 { if err != nil || len(toknames) != 1 {
t.Errorf("Get tokens: %v %v", err, toknames) t.Errorf("Get tokens: %v %v", err, toknames)
} }
tokname := toknames[0] tokname := toknames[0]
var globalToknames []string var globalToknames []string
err = getJSON("/galene-api/v0/.groups/test/.tokens/", &globalToknames) err = getJSON("/skald-api/v0/.groups/test/.tokens/", &globalToknames)
if err != nil || len(globalToknames) != 1 { if err != nil || len(globalToknames) != 1 {
t.Errorf("Get global tokens: %v %v", err, globalToknames) t.Errorf("Get global tokens: %v %v", err, globalToknames)
} }
@@ -336,7 +336,7 @@ func TestApi(t *testing.T) {
t.Errorf("token.List: %v %v", tokens, err) t.Errorf("token.List: %v %v", tokens, err)
} }
tokenpath := "/galene-api/v0/.groups/test/.tokens/" + tokname tokenpath := "/skald-api/v0/.groups/test/.tokens/" + tokname
var tok token.Stateful var tok token.Stateful
err = getJSON(tokenpath, &tok) err = getJSON(tokenpath, &tok)
if err != nil { if err != nil {
@@ -375,13 +375,13 @@ func TestApi(t *testing.T) {
t.Errorf("Got %v, expected %v (%v)", tok.Expires, e, err) t.Errorf("Got %v, expected %v (%v)", tok.Expires, e, err)
} }
resp, err = do("PUT", "/galene-api/v0/.groups/test2", resp, err = do("PUT", "/skald-api/v0/.groups/test2",
"application/json", "", "*", "{}") "application/json", "", "*", "{}")
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create test2: %v %v", err, resp.StatusCode) t.Errorf("Create test2: %v %v", err, resp.StatusCode)
} }
tokenpath2 := "/galene-api/v0/.groups/test2/.tokens/" + tokname tokenpath2 := "/skald-api/v0/.groups/test2/.tokens/" + tokname
resp, err = do("GET", tokenpath2, "", "", "", "") resp, err = do("GET", tokenpath2, "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNotFound { if err != nil || resp.StatusCode != http.StatusNotFound {
t.Errorf("Get token in bad group: %v %v", err, resp.StatusCode) t.Errorf("Get token in bad group: %v %v", err, resp.StatusCode)
@@ -402,13 +402,13 @@ func TestApi(t *testing.T) {
t.Errorf("Token list: %v %v", tokens, err) t.Errorf("Token list: %v %v", tokens, err)
} }
resp, err = do("DELETE", "/galene-api/v0/.groups/test/.keys", resp, err = do("DELETE", "/skald-api/v0/.groups/test/.keys",
"", "", "", "") "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete keys: %v %v", err, resp.StatusCode) t.Errorf("Delete keys: %v %v", err, resp.StatusCode)
} }
resp, err = do("DELETE", "/galene-api/v0/.groups/test/", resp, err = do("DELETE", "/skald-api/v0/.groups/test/",
"", "", "", "") "", "", "", "")
if err != nil || resp.StatusCode != http.StatusNoContent { if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("Delete group: %v %v", err, resp.StatusCode) t.Errorf("Delete group: %v %v", err, resp.StatusCode)
@@ -447,9 +447,9 @@ func TestApiBadAuth(t *testing.T) {
} }
} }
do("GET", "/galene-api/v0/.stats") do("GET", "/skald-api/v0/.stats")
do("GET", "/galene-api/v0/.groups/") do("GET", "/skald-api/v0/.groups/")
do("PUT", "/galene-api/v0/.groups/test/") do("PUT", "/skald-api/v0/.groups/test/")
f, err := os.Create(filepath.Join(group.Directory, "test.json")) f, err := os.Create(filepath.Join(group.Directory, "test.json"))
if err != nil { if err != nil {
@@ -460,20 +460,20 @@ func TestApiBadAuth(t *testing.T) {
}\n`) }\n`)
f.Close() f.Close()
do("PUT", "/galene-api/v0/.groups/test/") do("PUT", "/skald-api/v0/.groups/test/")
do("DELETE", "/galene-api/v0/.groups/test/") do("DELETE", "/skald-api/v0/.groups/test/")
do("GET", "/galene-api/v0/.groups/test/.users/") do("GET", "/skald-api/v0/.groups/test/.users/")
do("GET", "/galene-api/v0/.groups/test/.users/jch") do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/galene-api/v0/.groups/test/.users/jch") do("GET", "/skald-api/v0/.groups/test/.users/jch")
do("PUT", "/galene-api/v0/.groups/test/.users/jch") do("PUT", "/skald-api/v0/.groups/test/.users/jch")
do("DELETE", "/galene-api/v0/.groups/test/.users/jch") do("DELETE", "/skald-api/v0/.groups/test/.users/jch")
do("GET", "/galene-api/v0/.groups/test/.users/not-jch") do("GET", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/galene-api/v0/.groups/test/.users/not-jch") do("PUT", "/skald-api/v0/.groups/test/.users/not-jch")
do("PUT", "/galene-api/v0/.groups/test/.users/jch/.password") do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password")
do("POST", "/galene-api/v0/.groups/test/.users/jch/.password") do("POST", "/skald-api/v0/.groups/test/.users/jch/.password")
do("GET", "/galene-api/v0/.groups/test/.tokens/") do("GET", "/skald-api/v0/.groups/test/.tokens/")
do("POST", "/galene-api/v0/.groups/test/.tokens/") do("POST", "/skald-api/v0/.groups/test/.tokens/")
do("GET", "/galene-api/v0/.groups/test/.tokens/token") do("GET", "/skald-api/v0/.groups/test/.tokens/token")
do("PUT", "/galene-api/v0/.groups/test/.tokens/token") do("PUT", "/skald-api/v0/.groups/test/.tokens/token")
do("DELETE", "/galene-api/v0/.groups/test/.tokens/token") do("DELETE", "/skald-api/v0/.groups/test/.tokens/token")
} }
+5 -5
View File
@@ -21,10 +21,10 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"git.stormux.org/storm/skald/diskwriter"
"git.stormux.org/storm/skald/group"
"git.stormux.org/storm/skald/rtpconn"
"github.com/jech/cert" "github.com/jech/cert"
"github.com/jech/galene/diskwriter"
"github.com/jech/galene/group"
"github.com/jech/galene/rtpconn"
) )
var server *http.Server var server *http.Server
@@ -44,7 +44,7 @@ func Serve(address string, dataDir string) error {
http.HandleFunc("/recordings/", recordingsHandler) http.HandleFunc("/recordings/", recordingsHandler)
http.HandleFunc("/ws", wsHandler) http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/public-groups.json", publicHandler) http.HandleFunc("/public-groups.json", publicHandler)
http.HandleFunc("/galene-api/", apiHandler) http.HandleFunc("/skald-api/", apiHandler)
s := &http.Server{ s := &http.Server{
Addr: address, Addr: address,
@@ -371,7 +371,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
status := g.Status(false, nil) status := g.Status(false, nil)
cspHeader(w, status.AuthServer) cspHeader(w, status.AuthServer)
serveFile(w, r, filepath.Join(StaticRoot, "galene.html")) serveFile(w, r, filepath.Join(StaticRoot, "skald.html"))
} }
func baseURL(r *http.Request) (*url.URL, error) { func baseURL(r *http.Request) (*url.URL, error) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
) )
func TestParseGroupName(t *testing.T) { func TestParseGroupName(t *testing.T) {
+4 -4
View File
@@ -17,10 +17,10 @@ import (
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/jech/galene/group" "git.stormux.org/storm/skald/group"
"github.com/jech/galene/ice" "git.stormux.org/storm/skald/ice"
"github.com/jech/galene/rtpconn" "git.stormux.org/storm/skald/rtpconn"
"github.com/jech/galene/sdpfrag" "git.stormux.org/storm/skald/sdpfrag"
) )
var idSecret []byte var idSecret []byte