Rename public group URLs to halls

This commit is contained in:
Storm Dragon
2026-05-17 22:22:46 -04:00
parent bda0e548d3
commit 55528b8e62
21 changed files with 312 additions and 312 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/n
./skald & ./skald &
``` ```
Point your browser at <https://localhost:8443/group/night-watch/>, ignore Point your browser at <https://localhost:8443/hall/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*.
+21 -21
View File
@@ -1,22 +1,22 @@
# Skald's administrative API # Skald's administrative API
Skald 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 hall, a client may do
PUT /skald-api/v0/.groups/groupname/ PUT /skald-api/v0/.halls/hallname/
Content-Type: application/json Content-Type: application/json
If-None-Match: * If-None-Match: *
The `If-None-Match` header avoids overwriting an existing group. 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 hall definition, a client first does
GET /skald-api/v0/.groups/groupname/ GET /skald-api/v0/.halls/hallname/
This yields the group definition and an entity tag (in the ETag header). This yields the hall 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 /skald-api/v0/.groups/groupname/ PUT /skald-api/v0/.halls/hallname/
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
@@ -41,23 +41,23 @@ allowed methods are HEAD and GET.
### List of groups ### List of groups
/skald-api/v0/.groups/ /skald-api/v0/.halls/
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
/skald-api/v0/.groups/groupname /skald-api/v0/.halls/hallname
Contains a "sanitised" group definition in JSON format, analogous to the Contains a "sanitised" hall 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.
Allowed methods are HEAD, GET, PUT and DELETE. The only accepted Allowed methods are HEAD, GET, PUT and DELETE. The only accepted
content-type is `application/json`. content-type is `application/json`.
### Authentication keys ### Authentication keys
/skald-api/v0/.groups/groupname/.keys /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.users/ /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.users/username /skald-api/v0/.halls/hallname/.users/username
/skald-api/v0/.groups/groupname/.empty-user /skald-api/v0/.halls/hallname/.empty-user
/skald-api/v0/.groups/groupname/.wildcard-user /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.users/username/.password /skald-api/v0/.halls/hallname/.users/username/.password
/skald-api/v0/.groups/groupname/.empty-user/.password /skald-api/v0/.halls/hallname/.empty-user/.password
/skald-api/v0/.groups/groupname/.wildcard-user/.password /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.wildcard-user /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.wildcard-user/.password /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.users/username/.tokens/ /skald-api/v0/.halls/hallname/.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
/skald-api/v0/.groups/groupname/.users/username/.tokens/token /skald-api/v0/.halls/hallname/.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
+10 -10
View File
@@ -159,46 +159,46 @@ rsync config.json skald@skald.example.org:data/
Create a group: Create a group:
```sh ```sh
skaldctl create-group -group city-watch skaldctl create-hall -hall 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
`skaldctl` with the flag `-insecure`: `skaldctl` with the flag `-insecure`:
```sh ```sh
skaldctl -insecure create-group -group city-watch skaldctl -insecure create-hall -hall city-watch
``` ```
Create an "op", a user with group moderation privileges: Create an "op", a user with group moderation privileges:
```sh ```sh
skaldctl create-user -group city-watch -user vimes -permissions op skaldctl create-user -hall city-watch -user vimes -permissions op
``` ```
Set the new user's password: Set the new user's password:
```sh ```sh
skaldctl set-password -group city-watch -user vimes skaldctl set-password -hall city-watch -user vimes
``` ```
You should now be able to test your Skald installation by pointing a web You should now be able to test your Skald installation by pointing a web
browser at <https://skald.example.org:8443/group/city-watch/>. browser at <https://skald.example.org:8443/hall/city-watch/>.
Create an ordinary user: Create an ordinary user:
```sh ```sh
skaldctl create-user -group city-watch -user fred skaldctl create-user -hall city-watch -user fred
skaldctl set-password -group city-watch -user fred skaldctl set-password -hall city-watch -user fred
``` ```
Check the results: Check the results:
```sh ```sh
skaldctl list-groups skaldctl list-halls
skaldctl list-users -l -group city-watch skaldctl list-users -l -hall city-watch
``` ```
Type `skaldctl -help`, `skaldctl create-group -help`, etc. for more Type `skaldctl -help`, `skaldctl create-hall -help`, etc. for more
information. information.
## Advanced configuration ## Advanced configuration
+4 -4
View File
@@ -30,7 +30,7 @@ server to client direction.
The client needs to know the location of the group, the (user-visible) URL The client needs to know the location of the group, the (user-visible) URL
at which the group is found. This may be obtained either by explicit at which the group is found. This may be obtained either by explicit
configuration by the user, or by parsing the `/public-groups.json` file configuration by the user, or by parsing the `/public-halls.json` file
which may contain an array of group statuses (see below). which may contain an array of group statuses (see below).
A client then performs an HTTP GET request on the file `.status` at A client then performs an HTTP GET request on the file `.status` at
@@ -558,7 +558,7 @@ 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://skald.example.org/group/groupname/", "location": "https://skald.example.org/hall/hallname/",
"username": username, "username": username,
"password": password "password": password
} }
@@ -575,7 +575,7 @@ a signed JWT (a "JWS") the body of which has the following form:
```javascript ```javascript
{ {
"sub": username, "sub": username,
"aud": "https://skald.example.org/group/groupname/", "aud": "https://skald.example.org/hall/hallname/",
"permissions": ["present"], "permissions": ["present"],
"iat": now, "iat": now,
"exp": now + 30s, "exp": now + 30s,
@@ -599,4 +599,4 @@ 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://skald.example.org/group/groupname/?token=eyJhbG... https://skald.example.org/hall/hallname/?token=eyJhbG...
+32 -32
View File
@@ -23,7 +23,7 @@ your browser directly at the desired group URL.
### The group pages ### The group pages
A group named e.g. *city-watch* has an associated page A group named e.g. *city-watch* has an associated page
`/group/city-watch/`. After login, it presents an interface consisting `/hall/city-watch/`. After login, it presents an interface consisting
of three panes: of three panes:
- the left side pane contains the list of users who have joined the - the left side pane contains the list of users who have joined the
@@ -71,7 +71,7 @@ a *user menu*, a menu that applies to that specific user.
The centre pane is a traditional chat interface, with an input form at the The centre pane is a traditional chat interface, with an input form at the
bottom and the chat history above it. Chat history is never saved to bottom and the chat history above it. Chat history is never saved to
disk, and is erased after four hours (or whatever is specified in the disk, and is erased after four hours (or whatever is specified in the
`"max-history-age"` field of the group definition). `"max-history-age"` field of the hall definition).
Double-clicking on a message opens a contextual menu. Double-clicking on a message opens a contextual menu.
@@ -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://skald.example.org:8443/group/city-watch/?token=XXX https://skald.example.org:8443/hall/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
@@ -160,7 +160,7 @@ fields are as follows:
only meaningful permission is `"admin"`; only meaningful permission is `"admin"`;
- `writableGroups`: if true, then the API used by `skaldctl` 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 hall definitions; if unset or false, then only read-only
access is allowed; access is allowed;
- `allowOrigin` is an array containing the list of HTTP origins that - `allowOrigin` is an array containing the list of HTTP origins that
@@ -187,21 +187,21 @@ also be edited manually (there is no need to restart the server).
#### Creating, modifying, and deleting groups #### Creating, modifying, and deleting groups
A group is created using `skaldctl create-group`: A group is created using `skaldctl create-hall`:
```sh ```sh
skaldctl create-group -group city-watch skaldctl create-hall -hall 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
`skaldctl create-group -help` for a full list. For example, in order to `skaldctl create-hall -help` for a full list. For example, in order to
create a group that allows unrestricted creation of tokens, say: create a hall that allows unrestricted creation of tokens, say:
```sh ```sh
skaldctl create-group -group city-watch -unrestricted-tokens skaldctl create-hall -hall city-watch -unrestricted-tokens
``` ```
For more advanced configuration, `skaldctl create-group` can be invoked For more advanced configuration, `skaldctl create-hall` 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,27 +209,27 @@ 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://skald.example.org:8443/group/city-watch/"}' | skaldctl create-group -group amcw -json echo '{"redirect": "https://skald.example.org:8443/hall/city-watch/"}' | skaldctl create-hall -hall amcw -json
``` ```
Groups are modified using `skaldctl update-group`: Groups are modified using `skaldctl update-hall`:
```sh ```sh
skaldctl update-group -group city-watch -unrestricted-tokens=false skaldctl update-hall -hall city-watch -unrestricted-tokens=false
``` ```
If a JSON template is provided to `skaldctl update-group`, then it is If a JSON template is provided to `skaldctl update-hall`, 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}' | skaldctl update-group -group amcw echo '{"redirect": null}' | skaldctl update-hall -hall amcw
``` ```
A group is deleted using `skaldctl delete-group`: A group is deleted using `skaldctl delete-hall`:
```sh ```sh
skaldctl delete-group -group amcw skaldctl delete-hall -hall amcw
``` ```
#### Creating, modifying, and deleting users #### Creating, modifying, and deleting users
@@ -237,7 +237,7 @@ skaldctl delete-group -group amcw
A user entry is created with the `skaldctl create-user` command: A user entry is created with the `skaldctl create-user` command:
```sh ```sh
skaldctl create-user -group city-watch -user vimes -permissions op skaldctl create-user -hall 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`,
@@ -253,7 +253,7 @@ In order to be useful, a user entry needs to be assigned a password. This
is done with the `skaldctl set-password` command: is done with the `skaldctl set-password` command:
```sh ```sh
skaldctl set-password -group city-watch -user vimes skaldctl set-password -hall 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
skaldctl create-user -group city-watch -wildcard skaldctl create-user -hall city-watch -wildcard
skaldctl set-password -group city-watch -wildcard skaldctl set-password -hall 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
skaldctl set-password -group city-watch -wildcard -type wildcard skaldctl set-password -hall 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
@@ -291,7 +291,7 @@ Automatic subgroups are enabled by setting the `"auto-subgroups"`
field in the group description: field in the group description:
```sh ```sh
skaldctl create-group unseen-university -auto-subgroups skaldctl create-hall 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
@@ -308,8 +308,8 @@ the `skaldctl` utility's `create-token`, `revoke-token`, `delete-token`
and `list-tokens` commands: and `list-tokens` commands:
```sh ```sh
skaldctl create-token -group city-watch skaldctl create-token -hall city-watch
skaldctl list-tokens -l -group city-watch skaldctl list-tokens -l -hall 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,25 +317,25 @@ the whole hierarchy rooted at the given group, including both ordinary
groups and automatically generated subgroups. groups and automatically generated subgroups.
```sh ```sh
skaldctl create-token -group city-watch -include-subgroups skaldctl create-token -hall 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
skaldctl create-token -group '' -include-subgroups skaldctl create-token -hall '' -include-subgroups
``` ```
### Group description reference ### Group description reference
The definition for the group called *groupname* is in the file The definition for the group called *hallname* is in the file
`groups/groupname.json`; it does not contain the group name, which makes `groups/hallname.json`; it does not contain the group name, which makes
it easy to copy or link group definitions. You may use subdirectories: it easy to copy or link hall definitions. You may use subdirectories:
a file `groups/teaching/networking.json` defines a group called a file `groups/teaching/networking.json` defines a group called
*teaching/networking*. *teaching/networking*.
Every group definition file contains a single JSON dictionary. All fields Every hall definition file contains a single JSON dictionary. All fields
are optional. The following fields are allowed: are optional. The following fields are allowed:
- `users`: a dictionary that maps user names to user descriptions (see - `users`: a dictionary that maps user names to user descriptions (see
@@ -352,7 +352,7 @@ are optional. The following fields are allowed:
displayed at the top of the group page; displayed at the top of the group page;
- `description`: a human-readable description of the group; this is - `description`: a human-readable description of the group; this is
displayed on the landing page for public groups; displayed on the landing page for public halls;
- `contact`: a human-readable contact for this group, such as an e-mail - `contact`: a human-readable contact for this group, such as an e-mail
address, ignored by the server; address, ignored by the server;
+87 -87
View File
@@ -65,25 +65,25 @@ var commands = map[string]command{
command: deletePasswordCmd, command: deletePasswordCmd,
description: "delete a user's password", description: "delete a user's password",
}, },
"list-groups": { "list-halls": {
command: listGroupsCmd, command: listGroupsCmd,
description: "list groups", description: "list halls",
}, },
"show-group": { "show-hall": {
command: showGroupCmd, command: showGroupCmd,
description: "show group definition", description: "show hall definition",
}, },
"create-group": { "create-hall": {
command: createGroupCmd, command: createGroupCmd,
description: "create a group", description: "create a hall",
}, },
"update-group": { "update-hall": {
command: updateGroupCmd, command: updateGroupCmd,
description: "change a group's definition", description: "change a hall's definition",
}, },
"delete-group": { "delete-hall": {
command: deleteGroupCmd, command: deleteGroupCmd,
description: "delete a group", description: "delete a hall",
}, },
"list-users": { "list-users": {
command: listUsersCmd, command: listUsersCmd,
@@ -593,7 +593,7 @@ func deleteValue(url string) error {
} }
func setPasswordCmd(cmdname string, args []string) { func setPasswordCmd(cmdname string, args []string) {
var groupname, username string var hallname, username string
var wildcard bool var wildcard bool
var password, algorithm string var password, algorithm string
var iterations, cost, length, saltlen int var iterations, cost, length, saltlen int
@@ -603,7 +603,7 @@ func setPasswordCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`") cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password") cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password")
cmd.StringVar(&password, "password", "", "new `password`") cmd.StringVar(&password, "password", "", "new `password`")
@@ -622,9 +622,9 @@ func setPasswordCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
@@ -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, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user/.password", ".wildcard-user/.password",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".password", ".users", username, ".password",
) )
} }
@@ -675,7 +675,7 @@ func setPasswordCmd(cmdname string, args []string) {
} }
func deletePasswordCmd(cmdname string, args []string) { func deletePasswordCmd(cmdname string, args []string) {
var groupname, username string var hallname, username string
var wildcard bool var wildcard bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -683,7 +683,7 @@ func deletePasswordCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`") cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password") cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password")
cmd.Parse(args) cmd.Parse(args)
@@ -693,9 +693,9 @@ func deletePasswordCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
@@ -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, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user/.password", ".wildcard-user/.password",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".password", ".users", username, ".password",
) )
} }
@@ -796,7 +796,7 @@ func stdinJSON(doit bool) (map[string]any, error) {
} }
func createGroupCmd(cmdname string, args []string) { func createGroupCmd(cmdname string, args []string) {
var groupname string var hallname string
var unrestrictedTokens, autoSubgroups boolOption var unrestrictedTokens, autoSubgroups boolOption
var doJSON bool var doJSON bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -804,7 +804,7 @@ func createGroupCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Var(&unrestrictedTokens, "unrestricted-tokens", cmd.Var(&unrestrictedTokens, "unrestricted-tokens",
"allow ordinary users to create tokens", "allow ordinary users to create tokens",
) )
@@ -821,14 +821,14 @@ func createGroupCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -848,18 +848,18 @@ func createGroupCmd(cmdname string, args []string) {
err = putJSON(u, data, false) err = putJSON(u, data, false)
if err != nil { if err != nil {
log.Fatalf("Create group: %v", err) log.Fatalf("Create hall: %v", err)
} }
} }
func deleteGroupCmd(cmdname string, args []string) { func deleteGroupCmd(cmdname string, args []string) {
var groupname string var hallname 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(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Parse(args) cmd.Parse(args)
if cmd.NArg() != 0 { if cmd.NArg() != 0 {
@@ -867,14 +867,14 @@ func deleteGroupCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -882,12 +882,12 @@ func deleteGroupCmd(cmdname string, args []string) {
err = deleteValue(u) err = deleteValue(u)
if err != nil { if err != nil {
log.Fatalf("Delete group: %v", err) log.Fatalf("Delete hall: %v", err)
} }
} }
func updateGroupCmd(cmdname string, args []string) { func updateGroupCmd(cmdname string, args []string) {
var groupname string var hallname string
var unrestrictedTokens, autoSubgroups boolOption var unrestrictedTokens, autoSubgroups boolOption
var doJSON bool var doJSON bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
@@ -895,7 +895,7 @@ func updateGroupCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Var(&unrestrictedTokens, "unrestricted-tokens", cmd.Var(&unrestrictedTokens, "unrestricted-tokens",
"allow ordinary users to create tokens", "allow ordinary users to create tokens",
) )
@@ -913,7 +913,7 @@ func updateGroupCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1007,25 +1007,25 @@ func match(patterns []string, value string) (bool, error) {
} }
func listUsersCmd(cmdname string, args []string) { func listUsersCmd(cmdname string, args []string) {
var groupname string var hallname string
var long bool var long bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, setUsage(cmd, cmdname,
"%v [option...] %v [option...] [pattern...]\n", "%v [option...] %v [option...] [pattern...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.BoolVar(&long, "l", false, "display permissions") cmd.BoolVar(&long, "l", false, "display permissions")
cmd.Parse(args) cmd.Parse(args)
patterns := cmd.Args() patterns := cmd.Args()
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname, u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/", hallname,
".users/") ".users/")
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1069,21 +1069,21 @@ func listUsersCmd(cmdname string, args []string) {
} }
} }
func userURL(wildcard bool, groupname, username string) (string, error) { func userURL(wildcard bool, hallname, username string) (string, error) {
if wildcard { if wildcard {
return url.JoinPath( return url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user", ".wildcard-user",
) )
} }
return url.JoinPath( return url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".users", username,
) )
} }
func createUserCmd(cmdname string, args []string) { func createUserCmd(cmdname string, args []string) {
var groupname, username string var hallname, username string
var wildcard bool var wildcard bool
var permissions stringOption var permissions stringOption
var doJSON bool var doJSON bool
@@ -1092,7 +1092,7 @@ func createUserCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`") cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "create the wildcard user") cmd.BoolVar(&wildcard, "wildcard", false, "create the wildcard user")
cmd.Var(&permissions, "permissions", cmd.Var(&permissions, "permissions",
@@ -1107,9 +1107,9 @@ func createUserCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
@@ -1131,7 +1131,7 @@ func createUserCmd(cmdname string, args []string) {
} }
} }
u, err := userURL(wildcard, groupname, username) u, err := userURL(wildcard, hallname, username)
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1155,7 +1155,7 @@ func createUserCmd(cmdname string, args []string) {
} }
func updateUserCmd(cmdname string, args []string) { func updateUserCmd(cmdname string, args []string) {
var groupname, username string var hallname, username string
var wildcard bool var wildcard bool
var permissions stringOption var permissions stringOption
var doJSON bool var doJSON bool
@@ -1164,7 +1164,7 @@ func updateUserCmd(cmdname string, args []string) {
"%v [option...] %v [option...]\n", "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`") cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "update the wildcard user") cmd.BoolVar(&wildcard, "wildcard", false, "update the wildcard user")
cmd.Var(&permissions, "permissions", "permissions") cmd.Var(&permissions, "permissions", "permissions")
@@ -1175,7 +1175,7 @@ func updateUserCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
u, err := userURL(wildcard, groupname, username) u, err := userURL(wildcard, hallname, username)
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1217,13 +1217,13 @@ func updateUserCmd(cmdname string, args []string) {
} }
func deleteUserCmd(cmdname string, args []string) { func deleteUserCmd(cmdname string, args []string) {
var groupname, username string var hallname, username string
var wildcard bool var wildcard bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.StringVar(&username, "user", "", "user `name`") cmd.StringVar(&username, "user", "", "user `name`")
cmd.BoolVar(&wildcard, "wildcard", false, "delete the wildcard user") cmd.BoolVar(&wildcard, "wildcard", false, "delete the wildcard user")
cmd.Parse(args) cmd.Parse(args)
@@ -1233,9 +1233,9 @@ func deleteUserCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
@@ -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, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".wildcard-user", ".wildcard-user",
) )
} else { } else {
u, err = url.JoinPath( u, err = url.JoinPath(
serverURL, "/skald-api/v0/.groups", groupname, serverURL, "/skald-api/v0/.halls", hallname,
".users", username, ".users", username,
) )
} }
@@ -1270,13 +1270,13 @@ func deleteUserCmd(cmdname string, args []string) {
} }
func showGroupCmd(cmdname string, args []string) { func showGroupCmd(cmdname string, args []string) {
var groupname string var hallname string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, setUsage(cmd, cmdname,
"%v [option...] %v\n", "%v [option...] %v\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.StringVar(&groupname, "group", "", "group `name`") cmd.StringVar(&hallname, "hall", "", "hall `name`")
cmd.Parse(args) cmd.Parse(args)
if cmd.NArg() != 0 { if cmd.NArg() != 0 {
@@ -1284,11 +1284,11 @@ func showGroupCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if groupname == "" { if hallname == "" {
log.Fatal("Option \"-group\" is required.") log.Fatal("Option \"-hall\" is required.")
} }
u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname) u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/", hallname)
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1296,7 +1296,7 @@ func showGroupCmd(cmdname string, args []string) {
var description map[string]any var description map[string]any
_, err = getJSON(u, &description) _, err = getJSON(u, &description)
if err != nil { if err != nil {
log.Fatalf("Get group description: %v", err) log.Fatalf("Get hall description: %v", err)
} }
encoder := json.NewEncoder(os.Stdout) encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
@@ -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, "/skald-api/v0/.groups/") u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/")
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
} }
@@ -1323,7 +1323,7 @@ func listGroupsCmd(cmdname string, args []string) {
var groups []string var groups []string
_, err = getJSON(u, &groups) _, err = getJSON(u, &groups)
if err != nil { if err != nil {
log.Fatalf("Get groups: %v", err) log.Fatalf("Get halls: %v", err)
} }
sort.Slice(groups, func(i, j int) bool { sort.Slice(groups, func(i, j int) bool {
return groups[i] < groups[j] return groups[i] < groups[j]
@@ -1343,13 +1343,13 @@ func listGroupsCmd(cmdname string, args []string) {
} }
func listTokensCmd(cmdname string, args []string) { func listTokensCmd(cmdname string, args []string) {
var groupname stringOption var hallname stringOption
var long bool var long bool
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.Var(&groupname, "group", "group `name`") cmd.Var(&hallname, "hall", "hall `name`")
cmd.BoolVar(&long, "l", false, "display token fields") cmd.BoolVar(&long, "l", false, "display token fields")
cmd.Parse(args) cmd.Parse(args)
@@ -1358,14 +1358,14 @@ func listTokensCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if !groupname.set { if !hallname.set {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/", serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/",
) )
if err != nil { if err != nil {
@@ -1430,14 +1430,14 @@ func listTokensCmd(cmdname string, args []string) {
} }
func createTokenCmd(cmdname string, args []string) { func createTokenCmd(cmdname string, args []string) {
var groupname stringOption var hallname stringOption
var username, permissions string var username, permissions string
var includeSubgroups boolOption var includeSubgroups boolOption
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.Var(&groupname, "group", "group `name`") cmd.Var(&hallname, "hall", "hall `name`")
cmd.Var(&includeSubgroups, "include-subgroups", "include subgroups") cmd.Var(&includeSubgroups, "include-subgroups", "include subgroups")
cmd.StringVar(&username, "user", "", "encode user `name` in token") cmd.StringVar(&username, "user", "", "encode user `name` in token")
cmd.StringVar(&permissions, "permissions", "present", "permissions") cmd.StringVar(&permissions, "permissions", "present", "permissions")
@@ -1448,9 +1448,9 @@ func createTokenCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if !groupname.set { if !hallname.set {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Option \"-group\" is required\n") "Option \"-hall\" is required\n")
os.Exit(1) os.Exit(1)
} }
@@ -1469,7 +1469,7 @@ func createTokenCmd(cmdname string, args []string) {
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/", serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/",
) )
if err != nil { if err != nil {
log.Fatalf("Build URL: %v", err) log.Fatalf("Build URL: %v", err)
@@ -1483,13 +1483,13 @@ func createTokenCmd(cmdname string, args []string) {
} }
func revokeTokenCmd(cmdname string, args []string) { func revokeTokenCmd(cmdname string, args []string) {
var groupname stringOption var hallname stringOption
var token string var token string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.Var(&groupname, "group", "group `name`") cmd.Var(&hallname, "hall", "hall `name`")
cmd.StringVar(&token, "token", "", "`token` to delete") cmd.StringVar(&token, "token", "", "`token` to delete")
cmd.Parse(args) cmd.Parse(args)
@@ -1498,14 +1498,14 @@ func revokeTokenCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if !groupname.set || token == "" { if !hallname.set || token == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Options \"-group\" and \"-token\" are required\n") "Options \"-hall\" and \"-token\" are required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, serverURL, "/skald-api/v0/.halls/", hallname.value,
".tokens", token, ".tokens", token,
) )
if err != nil { if err != nil {
@@ -1522,13 +1522,13 @@ func revokeTokenCmd(cmdname string, args []string) {
} }
func deleteTokenCmd(cmdname string, args []string) { func deleteTokenCmd(cmdname string, args []string) {
var groupname stringOption var hallname stringOption
var token string var token string
cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) cmd := flag.NewFlagSet(cmdname, flag.ExitOnError)
setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", setUsage(cmd, cmdname, "%v [option...] %v [option...]\n",
os.Args[0], cmdname, os.Args[0], cmdname,
) )
cmd.Var(&groupname, "group", "group `name`") cmd.Var(&hallname, "hall", "hall `name`")
cmd.StringVar(&token, "token", "", "`token` to delete") cmd.StringVar(&token, "token", "", "`token` to delete")
cmd.Parse(args) cmd.Parse(args)
@@ -1537,14 +1537,14 @@ func deleteTokenCmd(cmdname string, args []string) {
os.Exit(1) os.Exit(1)
} }
if !groupname.set || token == "" { if !hallname.set || token == "" {
fmt.Fprintf(cmd.Output(), fmt.Fprintf(cmd.Output(),
"Options \"-group\" and \"-token\" are required\n") "Options \"-hall\" and \"-token\" are required\n")
os.Exit(1) os.Exit(1)
} }
u, err := url.JoinPath( u, err := url.JoinPath(
serverURL, "/skald-api/v0/.groups/", groupname.value, serverURL, "/skald-api/v0/.halls/", hallname.value,
".tokens", token, ".tokens", token,
) )
if err != nil { if err != nil {
+1 -1
View File
@@ -307,7 +307,7 @@ document.getElementById('start').onclick = async function(e) {
let button = /** @type{HTMLButtonElement} */(this); let button = /** @type{HTMLButtonElement} */(this);
button.hidden = true; button.hidden = true;
try { try {
await start("/group/public/"); await start("/hall/public/");
} catch(e) { } catch(e) {
displayError(e); displayError(e);
}; };
+7 -7
View File
@@ -15,22 +15,22 @@
<div class="home"> <div class="home">
<h1 id="title" class="navbar-brand">Skald</h1> <h1 id="title" class="navbar-brand">Skald</h1>
<form id="groupform"> <form id="hallform">
<label for="group">Group:</label> <label for="hall">Hall:</label>
<input id="group" type="text" name="group" class="form-control form-control-inline" autofocus/> <input id="hall" type="text" name="hall" class="form-control form-control-inline" autofocus/>
<input id='submitbutton' type="submit" value="Join" class="btn btn-default btn-large"/><br/> <input id='submitbutton' type="submit" value="Join" class="btn btn-default btn-large"/><br/>
</form> </form>
<p id="errormessage"></p> <p id="errormessage"></p>
<div id="public-groups" class="groups"> <div id="public-halls" class="groups">
<h2>Public groups</h2> <h2>Public halls</h2>
<table id="public-groups-table"></table> <table id="public-halls-table"></table>
</div> </div>
</div> </div>
<footer class="signature"> <footer class="signature">
<p><a href="https://skald.org/">Skald</a> by <a href="https://www.irif.fr/~jch/" rel="author">Juliusz Chroboczek</a></p> <p>Skald is derived from <a href="https://galene.org/">Galene</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>
+1 -1
View File
@@ -26,7 +26,7 @@ body {
padding: 1.875rem; padding: 1.875rem;
} }
#public-groups-table tr a{ #public-halls-table tr a{
margin-left: 0.9375rem; margin-left: 0.9375rem;
font-weight: 700; font-weight: 700;
} }
+10 -10
View File
@@ -20,16 +20,16 @@
'use strict'; 'use strict';
document.getElementById('groupform').onsubmit = async function(e) { document.getElementById('hallform').onsubmit = async function(e) {
e.preventDefault(); e.preventDefault();
clearError(); clearError();
let groupinput = document.getElementById('group') let groupinput = document.getElementById('hall')
let button = document.getElementById('submitbutton'); let button = document.getElementById('submitbutton');
let group = groupinput.value.trim(); let group = groupinput.value.trim();
if(group === '') if(group === '')
return; return;
let url = '/group/' + group + '/'; let url = '/hall/' + group + '/';
let statusUrl = url + '.status.json'; let statusUrl = url + '.status.json';
try { try {
@@ -78,25 +78,25 @@ function clearError() {
} }
} }
async function listPublicGroups() { async function listPublicHalls() {
let div = document.getElementById('public-groups'); let div = document.getElementById('public-halls');
let table = document.getElementById('public-groups-table'); let table = document.getElementById('public-halls-table');
let l; let l;
try { try {
let r = await fetch('/public-groups.json'); let r = await fetch('/public-halls.json');
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();
} catch(e) { } catch(e) {
table.textContent = `Couldn't fetch groups: ${e}`; table.textContent = `Couldn't fetch halls: ${e}`;
div.classList.remove('nogroups'); div.classList.remove('nogroups');
div.classList.add('groups'); div.classList.add('groups');
return; return;
} }
if (l.length === 0) { if (l.length === 0) {
table.textContent = '(No groups found.)'; table.textContent = '(No halls found.)';
div.classList.remove('groups'); div.classList.remove('groups');
div.classList.add('nogroups'); div.classList.add('nogroups');
return; return;
@@ -131,4 +131,4 @@ async function listPublicGroups() {
} }
listPublicGroups(); listPublicHalls();
+14 -14
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('/skald-api/v0/.groups/'); return await listObjects('/skald-api/v0/.halls/');
} }
/** /**
@@ -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(`/skald-api/v0/.groups/${group}`, etag); return await getObject(`/skald-api/v0/.halls/${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(`/skald-api/v0/.groups/${group}`, values); return await createObject(`/skald-api/v0/.halls/${group}`, values);
} }
/** /**
@@ -190,11 +190,11 @@ async function createGroup(group, values) {
* @param {string} [etag] * @param {string} [etag]
*/ */
async function deleteGroup(group, etag) { async function deleteGroup(group, etag) {
return await deleteObject(`/skald-api/v0/.groups/${group}`, etag); return await deleteObject(`/skald-api/v0/.halls/${group}`, etag);
} }
/** /**
* updateGroup modifies a group definition. * updateGroup modifies a hall definition.
* Any fields present in values are overriden, any fields absent in values * Any fields present in values are overriden, any fields absent in values
* are left unchanged. * are left unchanged.
* *
@@ -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(`/skald-api/v0/.groups/${group}`, values); return await updateObject(`/skald-api/v0/.halls/${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(`/skald-api/v0/.groups/${group}/.users/`); return await listObjects(`/skald-api/v0/.halls/${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 `/skald-api/v0/.groups/${group}/.wildcard-user`; return `/skald-api/v0/.halls/${group}/.wildcard-user`;
else if(user === "") else if(user === "")
return `/skald-api/v0/.groups/${group}/.empty-user`; return `/skald-api/v0/.halls/${group}/.empty-user`;
else else
return `/skald-api/v0/.groups/${group}/.users/${user}` return `/skald-api/v0/.halls/${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(`/skald-api/v0/.groups/${group}/.tokens/`); return await listObjects(`/skald-api/v0/.halls/${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(`/skald-api/v0/.groups/${group}/.tokens/${token}`, return await getObject(`/skald-api/v0/.halls/${group}/.tokens/${token}`,
etag); etag);
} }
@@ -353,7 +353,7 @@ async function createToken(group, template) {
} }
let r = await fetch( let r = await fetch(
`/skald-api/v0/.groups/${group}/.tokens/`, `/skald-api/v0/.halls/${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(
`/skald-api/v0/.groups/${group}/.tokens/${token.token}`, `/skald-api/v0/.halls/${group}/.tokens/${token.token}`,
token, etag); token, etag);
} }
+9 -9
View File
@@ -83,20 +83,20 @@ X Search for remaining Galene names and classify each survivor as attribution/hi
# PHASE 3: URLS, API, PROTOCOL, AND CONTROL TOOL # PHASE 3: URLS, API, PROTOCOL, AND CONTROL TOOL
# ============================================================================ # ============================================================================
- Rename browser hall URLs from /group/name/ to /hall/name/ X Rename browser hall URLs from /group/name/ to /hall/name/
- Rename /public-groups.json to /public-halls.json X Rename /public-groups.json to /public-halls.json
- Rename /galene-api/ to /skald-api/ X Rename /galene-api/ to /skald-api/
- Rename API collection paths from .groups to .halls X Rename API collection paths from .groups to .halls
- Rename protocol join fields from group to hall - Rename protocol join fields from group to hall
- Rename protocol callbacks/events from joined group semantics to joined hall semantics - Rename protocol callbacks/events from joined group semantics to joined hall semantics
- 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/ X Update static/management.js to use /skald-api/v0/.halls/
- Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json X Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json
- Update stats endpoint naming only if it exposes Skald/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 X 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 X Update skaldctl help text and error messages
- Update API and webserver tests for /hall/ and /skald-api/ X 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 Skald is intentionally broken - Verify protocol backward compatibility with Skald is intentionally broken
+3 -3
View File
@@ -190,10 +190,10 @@ func parseJWT(token string, keys []map[string]any) (*JWT, error) {
func matchGroup(pth, group string, includeSubgroups bool) bool { func matchGroup(pth, group string, includeSubgroups bool) bool {
if !includeSubgroups { if !includeSubgroups {
return pth == "/group/"+group+"/" return pth == "/hall/"+group+"/"
} }
if !strings.HasPrefix(pth, "/group/") { if !strings.HasPrefix(pth, "/hall/") {
return false return false
} }
@@ -201,7 +201,7 @@ func matchGroup(pth, group string, includeSubgroups bool) bool {
return false return false
} }
return strings.HasPrefix("/group/"+group+"/", pth) return strings.HasPrefix("/hall/"+group+"/", pth)
} }
func (token *JWT) Check(host, group string, username *string) (string, []string, error) { func (token *JWT) Check(host, group string, username *string) (string, []string, error) {
+13 -13
View File
@@ -85,21 +85,21 @@ func TestMatchGroup(t *testing.T) {
s bool s bool
} }
good := []tt{ good := []tt{
{"/group/a/", "a", false}, {"/hall/a/", "a", false},
{"/group/a/b/", "a/b", false}, {"/hall/a/b/", "a/b", false},
{"/group/a/", "a", true}, {"/hall/a/", "a", true},
{"/group/a/", "a/b", true}, {"/hall/a/", "a/b", true},
{"/group/a/b/", "a/b", true}, {"/hall/a/b/", "a/b", true},
{"/group/a/b/", "a/b/c", true}, {"/hall/a/b/", "a/b/c", true},
} }
bad := []tt{ bad := []tt{
{"/group/a/", "b", false}, {"/hall/a/", "b", false},
{"/group/a", "a", false}, {"/hall/a", "a", false},
{"/group/a", "a", true}, {"/hall/a", "a", true},
{"/group/a/", "a/b", false}, {"/hall/a/", "a/b", false},
{"/group/a/b/", "a", false}, {"/hall/a/b/", "a", false},
{"/group/a/b/", "a", true}, {"/hall/a/b/", "a", true},
} }
for _, test := range good { for _, test := range good {
@@ -142,7 +142,7 @@ func TestJWT(t *testing.T) {
john := "john" john := "john"
jack := "jack" jack := "jack"
baseClaims := jwt.MapClaims{ baseClaims := jwt.MapClaims{
"aud": "https://skald.org:8443/group/auth/", "aud": "https://skald.org:8443/hall/auth/",
"permissions": []string{"present"}, "permissions": []string{"present"},
"iat": int64(1645310294), "iat": int64(1645310294),
"exp": int64(2906750294), "exp": int64(2906750294),
+16 -16
View File
@@ -46,27 +46,27 @@ func TestStatefulCheck(t *testing.T) {
user2 := "user2" user2 := "user2"
token1 := &Stateful{ token1 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
Username: &user, Username: &user,
Permissions: []string{"present", "message"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
} }
token2 := &Stateful{ token2 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
Permissions: []string{"present", "message"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
} }
token3 := &Stateful{ token3 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
Username: &user, Username: &user,
Permissions: []string{"present", "message"}, Permissions: []string{"present", "message"},
Expires: &past, Expires: &past,
} }
token4 := &Stateful{ token4 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
Username: &user, Username: &user,
Permissions: []string{"present", "message"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
@@ -74,7 +74,7 @@ func TestStatefulCheck(t *testing.T) {
} }
token5 := &Stateful{ token5 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
IncludeSubgroups: true, IncludeSubgroups: true,
Username: &user, Username: &user,
Permissions: []string{"present", "message"}, Permissions: []string{"present", "message"},
@@ -106,48 +106,48 @@ func TestStatefulCheck(t *testing.T) {
}{ }{
{ {
token: token1, token: token1,
group: "group", group: "hall",
username: &user, username: &user,
expUsername: user, expUsername: user,
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token1, token: token1,
group: "group", group: "hall",
username: &user2, username: &user2,
expUsername: user, expUsername: user,
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token1, token: token1,
group: "group", group: "hall",
expUsername: user, expUsername: user,
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token2, token: token2,
group: "group", group: "hall",
username: &user, username: &user,
expUsername: "", expUsername: "",
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token5, token: token5,
group: "group", group: "hall",
username: &user, username: &user,
expUsername: "user", expUsername: "user",
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token5, token: token5,
group: "group/subgroup", group: "hall/subhall",
username: &user, username: &user,
expUsername: "user", expUsername: "user",
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token7, token: token7,
group: "group/subgroup", group: "hall/subhall",
username: &user, username: &user,
expUsername: "user", expUsername: "user",
expPermissions: []string{"present", "message"}, expPermissions: []string{"present", "message"},
@@ -171,17 +171,17 @@ func TestStatefulCheck(t *testing.T) {
}{ }{
{ {
token: token1, token: token1,
group: "group2", group: "hall2",
username: &user, username: &user,
}, },
{ {
token: token3, token: token3,
group: "group", group: "hall",
username: &user, username: &user,
}, },
{ {
token: token4, token: token4,
group: "group", group: "hall",
username: &user, username: &user,
}, },
{ {
@@ -196,7 +196,7 @@ func TestStatefulCheck(t *testing.T) {
}, },
{ {
token: token6, token: token6,
group: "group", group: "hall",
username: &user, username: &user,
}, },
{ {
+2 -2
View File
@@ -25,7 +25,7 @@ func TestToken(t *testing.T) {
user := "user" user := "user"
_, err = Update(&Stateful{ _, err = Update(&Stateful{
Token: "token", Token: "token",
Group: "group", Group: "hall",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present"},
Expires: &future, Expires: &future,
@@ -39,7 +39,7 @@ func TestToken(t *testing.T) {
t.Fatalf("Parse: %v", err) t.Fatalf("Parse: %v", err)
} }
_, _, err = token.Check("skald.org:8443", "group", &user) _, _, err = token.Check("skald.org:8443", "hall", &user)
if err != nil { if err != nil {
t.Errorf("Check: %v", err) t.Errorf("Check: %v", err)
} }
+4 -4
View File
@@ -34,7 +34,7 @@ func checkAdmin(w http.ResponseWriter, r *http.Request) bool {
// checkPasswordAdmin checks whether the client authentifies as either an // checkPasswordAdmin checks whether the client authentifies as either an
// administrator or the given user. It is used to check whether the // administrator or the given user. It is used to check whether the
// client has the right to change user's password. // client has the right to change user's password.
func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user string, wildcard bool) bool { func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, hallname, user string, wildcard bool) bool {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if ok { if ok {
ok, err := adminMatch(username, password) ok, err := adminMatch(username, password)
@@ -47,11 +47,11 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user
} }
} }
if ok && !wildcard && username == user { if ok && !wildcard && username == user {
desc, err := group.GetDescription(groupname) desc, err := group.GetDescription(hallname)
if err != nil { if err != nil {
internalError(w, internalError(w,
"Get description for group %v: %v", "Get description for group %v: %v",
groupname, err, hallname, err,
) )
return false return false
} }
@@ -169,7 +169,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
} }
w.Header().Set("cache-control", "no-cache") w.Header().Set("cache-control", "no-cache")
sendJSON(w, r, stats.GetGroups()) sendJSON(w, r, stats.GetGroups())
case ".groups": case ".halls":
apiGroupHandler(w, r, rest) apiGroupHandler(w, r, rest)
default: default:
http.NotFound(w, r) http.NotFound(w, r)
+59 -59
View File
@@ -115,58 +115,58 @@ func TestApi(t *testing.T) {
} }
var groups []string var groups []string
err = getJSON("/skald-api/v0/.groups/", &groups) err = getJSON("/skald-api/v0/.halls/", &groups)
if err != nil || len(groups) != 0 { if err != nil || len(groups) != 0 {
t.Errorf("Get groups: %v", err) t.Errorf("Get halls: %v", err)
} }
resp, err := do("PUT", "/skald-api/v0/.groups/test/", resp, err := do("PUT", "/skald-api/v0/.halls/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 hall (bad ETag): %v %v", err, resp.StatusCode)
} }
resp, err = do("PUT", "/skald-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.halls/test/",
"text/plain", "", "", "text/plain", "", "",
"Hello, world!") "Hello, world!")
if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType {
t.Errorf("Create group (bad content-type): %v %v", t.Errorf("Create hall (bad content-type): %v %v",
err, resp.StatusCode) err, resp.StatusCode)
} }
resp, err = do("PUT", "/skald-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.halls/test/",
"application/json", "", "*", "application/json", "", "*",
"{}") "{}")
if err != nil || resp.StatusCode != http.StatusCreated { if err != nil || resp.StatusCode != http.StatusCreated {
t.Errorf("Create group: %v %v", err, resp.StatusCode) t.Errorf("Create hall: %v %v", err, resp.StatusCode)
} }
var desc *group.Description var desc *group.Description
err = getJSON("/skald-api/v0/.groups/test/", &desc) err = getJSON("/skald-api/v0/.halls/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 hall: %v", err)
} }
resp, err = do("PUT", "/skald-api/v0/.groups/test/", resp, err = do("PUT", "/skald-api/v0/.halls/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 hall (bad ETag): %v %v", err, resp.StatusCode)
} }
resp, err = do("DELETE", "/skald-api/v0/.groups/test/", resp, err = do("DELETE", "/skald-api/v0/.halls/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 hall (bad ETag): %v %v", err, resp.StatusCode)
} }
err = getJSON("/skald-api/v0/.groups/", &groups) err = getJSON("/skald-api/v0/.halls/", &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 halls: %v %v", err, groups)
} }
resp, err = do("PUT", "/skald-api/v0/.groups/test/.keys", resp, err = do("PUT", "/skald-api/v0/.halls/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("/skald-api/v0/.groups/test/.users/", &groups) err = getJSON("/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.halls/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("/skald-api/v0/.groups/test/.users/", &users) err = getJSON("/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.users/jch", resp, err = do("PUT", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.users/jch/.password", resp, err = do("PUT", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.users/jch/.password", resp, err = do("POST", "/skald-api/v0/.halls/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("/skald-api/v0/.groups/test/.users/jch", &user) err = getJSON("/skald-api/v0/.halls/test/.users/jch", &user)
if err != nil { if err != nil {
t.Errorf("Get user: %v", err) t.Errorf("Get user: %v", err)
} }
@@ -245,10 +245,10 @@ 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", "/skald-api/v0/.groups/test/.users/jch", resp, err = do("DELETE", "/skald-api/v0/.halls/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 hall: %v %v", err, resp.StatusCode)
} }
desc, err = group.GetDescription("test") desc, err = group.GetDescription("test")
@@ -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", "/skald-api/v0/.groups/test/.wildcard-user", resp, err = do("PUT", "/skald-api/v0/.halls/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("/skald-api/v0/.groups/test/.wildcard-user", &user) err = getJSON("/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.wildcard-user", resp, err = do("DELETE", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.tokens/", resp, err = do("POST", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/.tokens/", resp, err = do("POST", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/.tokens/", resp, err = do("POST", "/skald-api/v0/.halls/.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("/skald-api/v0/.groups/test/.tokens/", &toknames) err = getJSON("/skald-api/v0/.halls/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("/skald-api/v0/.groups/test/.tokens/", &globalToknames) err = getJSON("/skald-api/v0/.halls/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 := "/skald-api/v0/.groups/test/.tokens/" + tokname tokenpath := "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test2", resp, err = do("PUT", "/skald-api/v0/.halls/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 := "/skald-api/v0/.groups/test2/.tokens/" + tokname tokenpath2 := "/skald-api/v0/.halls/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,16 +402,16 @@ 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", "/skald-api/v0/.groups/test/.keys", resp, err = do("DELETE", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/", resp, err = do("DELETE", "/skald-api/v0/.halls/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 hall: %v %v", err, resp.StatusCode)
} }
_, err = group.GetDescription("test") _, err = group.GetDescription("test")
@@ -448,8 +448,8 @@ func TestApiBadAuth(t *testing.T) {
} }
do("GET", "/skald-api/v0/.stats") do("GET", "/skald-api/v0/.stats")
do("GET", "/skald-api/v0/.groups/") do("GET", "/skald-api/v0/.halls/")
do("PUT", "/skald-api/v0/.groups/test/") do("PUT", "/skald-api/v0/.halls/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", "/skald-api/v0/.groups/test/") do("PUT", "/skald-api/v0/.halls/test/")
do("DELETE", "/skald-api/v0/.groups/test/") do("DELETE", "/skald-api/v0/.halls/test/")
do("GET", "/skald-api/v0/.groups/test/.users/") do("GET", "/skald-api/v0/.halls/test/.users/")
do("GET", "/skald-api/v0/.groups/test/.users/jch") do("GET", "/skald-api/v0/.halls/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/jch") do("GET", "/skald-api/v0/.halls/test/.users/jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch") do("PUT", "/skald-api/v0/.halls/test/.users/jch")
do("DELETE", "/skald-api/v0/.groups/test/.users/jch") do("DELETE", "/skald-api/v0/.halls/test/.users/jch")
do("GET", "/skald-api/v0/.groups/test/.users/not-jch") do("GET", "/skald-api/v0/.halls/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/not-jch") do("PUT", "/skald-api/v0/.halls/test/.users/not-jch")
do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password") do("PUT", "/skald-api/v0/.halls/test/.users/jch/.password")
do("POST", "/skald-api/v0/.groups/test/.users/jch/.password") do("POST", "/skald-api/v0/.halls/test/.users/jch/.password")
do("GET", "/skald-api/v0/.groups/test/.tokens/") do("GET", "/skald-api/v0/.halls/test/.tokens/")
do("POST", "/skald-api/v0/.groups/test/.tokens/") do("POST", "/skald-api/v0/.halls/test/.tokens/")
do("GET", "/skald-api/v0/.groups/test/.tokens/token") do("GET", "/skald-api/v0/.halls/test/.tokens/token")
do("PUT", "/skald-api/v0/.groups/test/.tokens/token") do("PUT", "/skald-api/v0/.halls/test/.tokens/token")
do("DELETE", "/skald-api/v0/.groups/test/.tokens/token") do("DELETE", "/skald-api/v0/.halls/test/.tokens/token")
} }
+8 -8
View File
@@ -35,7 +35,7 @@ var Insecure bool
func Serve(address string, dataDir string) error { func Serve(address string, dataDir string) error {
http.Handle("/", &fileHandler{http.Dir(StaticRoot)}) http.Handle("/", &fileHandler{http.Dir(StaticRoot)})
http.HandleFunc("/group/", groupHandler) http.HandleFunc("/hall/", groupHandler)
http.HandleFunc("/recordings", http.HandleFunc("/recordings",
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, http.Redirect(w, r,
@@ -43,7 +43,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-halls.json", publicHandler)
http.HandleFunc("/skald-api/", apiHandler) http.HandleFunc("/skald-api/", apiHandler)
s := &http.Server{ s := &http.Server{
@@ -346,7 +346,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
name := parseGroupName("/group/", r.URL.Path) name := parseGroupName("/hall/", r.URL.Path)
if name == "" { if name == "" {
notFound(w) notFound(w)
return return
@@ -358,8 +358,8 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if r.URL.Path != "/group/"+name+"/" { if r.URL.Path != "/hall/"+name+"/" {
http.Redirect(w, r, "/group/"+name+"/", http.Redirect(w, r, "/hall/"+name+"/",
http.StatusPermanentRedirect) http.StatusPermanentRedirect)
return return
} }
@@ -415,7 +415,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
internalError(w, "groupStatusHandler: this shouldn't happen") internalError(w, "groupStatusHandler: this shouldn't happen")
return return
} }
name := parseGroupName("/group/", pth) name := parseGroupName("/hall/", pth)
if name == "" { if name == "" {
notFound(w) notFound(w)
return return
@@ -705,13 +705,13 @@ func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
} }
} }
func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname string) bool { func checkGroupPermissions(w http.ResponseWriter, r *http.Request, hallname string) bool {
user, pass, ok := r.BasicAuth() user, pass, ok := r.BasicAuth()
if !ok { if !ok {
return false return false
} }
g := group.Get(groupname) g := group.Get(hallname)
if g == nil { if g == nil {
return false return false
} }
+8 -8
View File
@@ -19,17 +19,17 @@ func TestParseGroupName(t *testing.T) {
{"/foo", ""}, {"/foo", ""},
{"foo", ""}, {"foo", ""},
{"group/foo", ""}, {"group/foo", ""},
{"/group", ""}, {"/hall", ""},
{"/group/..", ""}, {"/hall/..", ""},
{"/group/foo/../bar", "bar"}, {"/hall/foo/../bar", "bar"},
{"/group/foo", "foo"}, {"/hall/foo", "foo"},
{"/group/foo/", "foo"}, {"/hall/foo/", "foo"},
{"/group/foo/bar", "foo/bar"}, {"/hall/foo/bar", "foo/bar"},
{"/group/foo/bar/", "foo/bar"}, {"/hall/foo/bar/", "foo/bar"},
} }
for _, pg := range a { for _, pg := range a {
g := parseGroupName("/group/", pg.p) g := parseGroupName("/hall/", pg.p)
if g != pg.g { if g != pg.g {
t.Errorf("Path %v, got %v, expected %v", t.Errorf("Path %v, got %v, expected %v",
pg.p, g, pg.g) pg.p, g, pg.g)
+2 -2
View File
@@ -153,7 +153,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
name := parseGroupName("/group/", pth) name := parseGroupName("/hall/", pth)
if name == "" { if name == "" {
notFound(w) notFound(w)
return return
@@ -269,7 +269,7 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
name := parseGroupName("/group/", pth) name := parseGroupName("/hall/", pth)
if name == "" { if name == "" {
notFound(w) notFound(w)
return return