From 55528b8e627e133cfc9a8de0725070e5ff7c613c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 17 May 2026 22:22:46 -0400 Subject: [PATCH] Rename public group URLs to halls --- README.md | 2 +- skald-api.md | 42 ++++----- skald-install.md | 20 ++--- skald-protocol.md | 8 +- skald.md | 64 ++++++------- skaldctl/skaldctl.go | 174 ++++++++++++++++++------------------ static/example/example.js | 2 +- static/index.html | 14 +-- static/mainpage.css | 2 +- static/mainpage.js | 20 ++--- static/management.js | 28 +++--- todo.txt | 18 ++-- token/jwt.go | 6 +- token/jwt_test.go | 26 +++--- token/stateful_test.go | 32 +++---- token/token_test.go | 4 +- webserver/api.go | 8 +- webserver/api_test.go | 118 ++++++++++++------------ webserver/webserver.go | 16 ++-- webserver/webserver_test.go | 16 ++-- webserver/whip.go | 4 +- 21 files changed, 312 insertions(+), 312 deletions(-) diff --git a/README.md b/README.md index 64b0ed8..5e3737f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/n ./skald & ``` -Point your browser at , ignore +Point your browser at , ignore the unknown certificate warning, and log in with username *vimes* and password *sybil*. diff --git a/skald-api.md b/skald-api.md index 5f32215..d5e2189 100644 --- a/skald-api.md +++ b/skald-api.md @@ -1,22 +1,22 @@ # Skald's administrative API 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 If-None-Match: * 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 - PUT /skald-api/v0/.groups/groupname/ + PUT /skald-api/v0/.halls/hallname/ If-Match: "abcd" 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 - /skald-api/v0/.groups/ + /skald-api/v0/.halls/ Returns a list of groups, as a JSON array. The only allowed methods are HEAD and GET. ### 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. Allowed methods are HEAD, GET, PUT and DELETE. The only accepted content-type is `application/json`. ### 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 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 - /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 HEAD and GET. ### User definitions - /skald-api/v0/.groups/groupname/.users/username - /skald-api/v0/.groups/groupname/.empty-user - /skald-api/v0/.groups/groupname/.wildcard-user + /skald-api/v0/.halls/hallname/.users/username + /skald-api/v0/.halls/hallname/.empty-user + /skald-api/v0/.halls/hallname/.wildcard-user Contains a "sanitised" user definition (without any passwords), a JSON object with a single field `permissions`. The entries `.empty-user` and @@ -84,9 +84,9 @@ only accepted content-type is `application/json`. ### Passwords - /skald-api/v0/.groups/groupname/.users/username/.password - /skald-api/v0/.groups/groupname/.empty-user/.password - /skald-api/v0/.groups/groupname/.wildcard-user/.password + /skald-api/v0/.halls/hallname/.users/username/.password + /skald-api/v0/.halls/hallname/.empty-user/.password + /skald-api/v0/.halls/hallname/.wildcard-user/.password Contains the password of a given user. The PUT method takes a full password definition, identical to what can appear in the `"password"` @@ -97,7 +97,7 @@ POST. ### 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 the dictionary defining an ordinary user. Allowed methods are HEAD, GET, @@ -105,14 +105,14 @@ PUT and DELETE. ### 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 are PUT, POST and DELETE. ### 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 a new token, and returns its name in the `Location` header. Allowed @@ -120,7 +120,7 @@ methods are HEAD, GET and POST. ### 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 between versions, so a client should first GET a token, update one or more diff --git a/skald-install.md b/skald-install.md index 499365c..bec06cb 100644 --- a/skald-install.md +++ b/skald-install.md @@ -159,46 +159,46 @@ rsync config.json skald@skald.example.org:data/ Create a group: ```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 `skaldctl` with the flag `-insecure`: ```sh -skaldctl -insecure create-group -group city-watch +skaldctl -insecure create-hall -hall city-watch ``` Create an "op", a user with group moderation privileges: ```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: ```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 -browser at . +browser at . Create an ordinary user: ```sh -skaldctl create-user -group city-watch -user fred -skaldctl set-password -group city-watch -user fred +skaldctl create-user -hall city-watch -user fred +skaldctl set-password -hall city-watch -user fred ``` Check the results: ```sh -skaldctl list-groups -skaldctl list-users -l -group city-watch +skaldctl list-halls +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. ## Advanced configuration diff --git a/skald-protocol.md b/skald-protocol.md index ce80346..5d141fb 100644 --- a/skald-protocol.md +++ b/skald-protocol.md @@ -30,7 +30,7 @@ server to client direction. 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 -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). 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: ```javascript { - "location": "https://skald.example.org/group/groupname/", + "location": "https://skald.example.org/hall/hallname/", "username": username, "password": password } @@ -575,7 +575,7 @@ a signed JWT (a "JWS") the body of which has the following form: ```javascript { "sub": username, - "aud": "https://skald.example.org/group/groupname/", + "aud": "https://skald.example.org/hall/hallname/", "permissions": ["present"], "iat": now, "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 query parameter named `token`: - https://skald.example.org/group/groupname/?token=eyJhbG... + https://skald.example.org/hall/hallname/?token=eyJhbG... diff --git a/skald.md b/skald.md index 11d923f..2747ca7 100644 --- a/skald.md +++ b/skald.md @@ -23,7 +23,7 @@ your browser directly at the desired group URL. ### The group pages 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: - 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 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 -`"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. @@ -87,7 +87,7 @@ commands. In order to generate an invitation link, choose the entry *Invite user* in 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 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"`; - `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; - `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 -A group is created using `skaldctl create-group`: +A group is created using `skaldctl create-hall`: ```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 -`skaldctl create-group -help` for a full list. For example, in order to -create a group that allows unrestricted creation of tokens, say: +`skaldctl create-hall -help` for a full list. For example, in order to +create a hall that allows unrestricted creation of tokens, say: ```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 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 @@ -209,27 +209,27 @@ definition file (see below), except that it must not contain the fields (see the section *Group description reference* below): ```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 -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 by setting them to `null` in the template: ```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 -skaldctl delete-group -group amcw +skaldctl delete-hall -hall amcw ``` #### 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: ```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`, @@ -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: ```sh -skaldctl set-password -group city-watch -user vimes +skaldctl set-password -hall city-watch -user vimes ``` #### 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: ```sh -skaldctl create-user -group city-watch -wildcard -skaldctl set-password -group city-watch -wildcard +skaldctl create-user -hall city-watch -wildcard +skaldctl set-password -hall city-watch -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`: ```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 @@ -291,7 +291,7 @@ Automatic subgroups are enabled by setting the `"auto-subgroups"` field in the group description: ```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 @@ -308,8 +308,8 @@ the `skaldctl` utility's `create-token`, `revoke-token`, `delete-token` and `list-tokens` commands: ```sh -skaldctl create-token -group city-watch -skaldctl list-tokens -l -group city-watch +skaldctl create-token -hall city-watch +skaldctl list-tokens -l -hall city-watch ``` 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. ```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 therefore be valid for any group on the server: ```sh -skaldctl create-token -group '' -include-subgroups +skaldctl create-token -hall '' -include-subgroups ``` ### Group description reference -The definition for the group called *groupname* is in the file -`groups/groupname.json`; it does not contain the group name, which makes -it easy to copy or link group definitions. You may use subdirectories: +The definition for the group called *hallname* is in the file +`groups/hallname.json`; it does not contain the group name, which makes +it easy to copy or link hall definitions. You may use subdirectories: a file `groups/teaching/networking.json` defines a group called *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: - `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; - `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 address, ignored by the server; diff --git a/skaldctl/skaldctl.go b/skaldctl/skaldctl.go index d786f5d..4a1e1dc 100644 --- a/skaldctl/skaldctl.go +++ b/skaldctl/skaldctl.go @@ -65,25 +65,25 @@ var commands = map[string]command{ command: deletePasswordCmd, description: "delete a user's password", }, - "list-groups": { + "list-halls": { command: listGroupsCmd, - description: "list groups", + description: "list halls", }, - "show-group": { + "show-hall": { command: showGroupCmd, - description: "show group definition", + description: "show hall definition", }, - "create-group": { + "create-hall": { command: createGroupCmd, - description: "create a group", + description: "create a hall", }, - "update-group": { + "update-hall": { command: updateGroupCmd, - description: "change a group's definition", + description: "change a hall's definition", }, - "delete-group": { + "delete-hall": { command: deleteGroupCmd, - description: "delete a group", + description: "delete a hall", }, "list-users": { command: listUsersCmd, @@ -593,7 +593,7 @@ func deleteValue(url string) error { } func setPasswordCmd(cmdname string, args []string) { - var groupname, username string + var hallname, username string var wildcard bool var password, algorithm string var iterations, cost, length, saltlen int @@ -603,7 +603,7 @@ func setPasswordCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.StringVar(&username, "user", "", "user `name`") cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password") cmd.StringVar(&password, "password", "", "new `password`") @@ -622,9 +622,9 @@ func setPasswordCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } @@ -655,12 +655,12 @@ func setPasswordCmd(cmdname string, args []string) { var u string if wildcard { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".wildcard-user/.password", ) } else { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".users", username, ".password", ) } @@ -675,7 +675,7 @@ func setPasswordCmd(cmdname string, args []string) { } func deletePasswordCmd(cmdname string, args []string) { - var groupname, username string + var hallname, username string var wildcard bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) @@ -683,7 +683,7 @@ func deletePasswordCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.StringVar(&username, "user", "", "user `name`") cmd.BoolVar(&wildcard, "wildcard", false, "set wildcard user's password") cmd.Parse(args) @@ -693,9 +693,9 @@ func deletePasswordCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } @@ -710,12 +710,12 @@ func deletePasswordCmd(cmdname string, args []string) { var err error if wildcard { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".wildcard-user/.password", ) } else { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".users", username, ".password", ) } @@ -796,7 +796,7 @@ func stdinJSON(doit bool) (map[string]any, error) { } func createGroupCmd(cmdname string, args []string) { - var groupname string + var hallname string var unrestrictedTokens, autoSubgroups boolOption var doJSON bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) @@ -804,7 +804,7 @@ func createGroupCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.Var(&unrestrictedTokens, "unrestricted-tokens", "allow ordinary users to create tokens", ) @@ -821,14 +821,14 @@ func createGroupCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ) if err != nil { log.Fatalf("Build URL: %v", err) @@ -848,18 +848,18 @@ func createGroupCmd(cmdname string, args []string) { err = putJSON(u, data, false) if err != nil { - log.Fatalf("Create group: %v", err) + log.Fatalf("Create hall: %v", err) } } func deleteGroupCmd(cmdname string, args []string) { - var groupname string + var hallname string cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.Parse(args) if cmd.NArg() != 0 { @@ -867,14 +867,14 @@ func deleteGroupCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ) if err != nil { log.Fatalf("Build URL: %v", err) @@ -882,12 +882,12 @@ func deleteGroupCmd(cmdname string, args []string) { err = deleteValue(u) if err != nil { - log.Fatalf("Delete group: %v", err) + log.Fatalf("Delete hall: %v", err) } } func updateGroupCmd(cmdname string, args []string) { - var groupname string + var hallname string var unrestrictedTokens, autoSubgroups boolOption var doJSON bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) @@ -895,7 +895,7 @@ func updateGroupCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.Var(&unrestrictedTokens, "unrestricted-tokens", "allow ordinary users to create tokens", ) @@ -913,7 +913,7 @@ func updateGroupCmd(cmdname string, args []string) { } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ) if err != nil { log.Fatalf("Build URL: %v", err) @@ -1007,25 +1007,25 @@ func match(patterns []string, value string) (bool, error) { } func listUsersCmd(cmdname string, args []string) { - var groupname string + var hallname string var long bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...] [pattern...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.BoolVar(&long, "l", false, "display permissions") cmd.Parse(args) patterns := cmd.Args() - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } - u, err := url.JoinPath(serverURL, "/skald-api/v0/.groups/", groupname, + u, err := url.JoinPath(serverURL, "/skald-api/v0/.halls/", hallname, ".users/") if err != nil { 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 { return url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".wildcard-user", ) } return url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".users", username, ) } func createUserCmd(cmdname string, args []string) { - var groupname, username string + var hallname, username string var wildcard bool var permissions stringOption var doJSON bool @@ -1092,7 +1092,7 @@ func createUserCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.StringVar(&username, "user", "", "user `name`") cmd.BoolVar(&wildcard, "wildcard", false, "create the wildcard user") cmd.Var(&permissions, "permissions", @@ -1107,9 +1107,9 @@ func createUserCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") 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 { log.Fatalf("Build URL: %v", err) } @@ -1155,7 +1155,7 @@ func createUserCmd(cmdname string, args []string) { } func updateUserCmd(cmdname string, args []string) { - var groupname, username string + var hallname, username string var wildcard bool var permissions stringOption var doJSON bool @@ -1164,7 +1164,7 @@ func updateUserCmd(cmdname string, args []string) { "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.StringVar(&username, "user", "", "user `name`") cmd.BoolVar(&wildcard, "wildcard", false, "update the wildcard user") cmd.Var(&permissions, "permissions", "permissions") @@ -1175,7 +1175,7 @@ func updateUserCmd(cmdname string, args []string) { os.Exit(1) } - u, err := userURL(wildcard, groupname, username) + u, err := userURL(wildcard, hallname, username) if err != nil { log.Fatalf("Build URL: %v", err) } @@ -1217,13 +1217,13 @@ func updateUserCmd(cmdname string, args []string) { } func deleteUserCmd(cmdname string, args []string) { - var groupname, username string + var hallname, username string var wildcard bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.StringVar(&username, "user", "", "user `name`") cmd.BoolVar(&wildcard, "wildcard", false, "delete the wildcard user") cmd.Parse(args) @@ -1233,9 +1233,9 @@ func deleteUserCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { + if hallname == "" { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } @@ -1250,12 +1250,12 @@ func deleteUserCmd(cmdname string, args []string) { var err error if wildcard { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".wildcard-user", ) } else { u, err = url.JoinPath( - serverURL, "/skald-api/v0/.groups", groupname, + serverURL, "/skald-api/v0/.halls", hallname, ".users", username, ) } @@ -1270,13 +1270,13 @@ func deleteUserCmd(cmdname string, args []string) { } func showGroupCmd(cmdname string, args []string) { - var groupname string + var hallname string cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v\n", os.Args[0], cmdname, ) - cmd.StringVar(&groupname, "group", "", "group `name`") + cmd.StringVar(&hallname, "hall", "", "hall `name`") cmd.Parse(args) if cmd.NArg() != 0 { @@ -1284,11 +1284,11 @@ func showGroupCmd(cmdname string, args []string) { os.Exit(1) } - if groupname == "" { - log.Fatal("Option \"-group\" is required.") + if hallname == "" { + 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 { log.Fatalf("Build URL: %v", err) } @@ -1296,7 +1296,7 @@ func showGroupCmd(cmdname string, args []string) { var description map[string]any _, err = getJSON(u, &description) if err != nil { - log.Fatalf("Get group description: %v", err) + log.Fatalf("Get hall description: %v", err) } encoder := json.NewEncoder(os.Stdout) encoder.SetIndent("", " ") @@ -1315,7 +1315,7 @@ func listGroupsCmd(cmdname string, args []string) { cmd.Parse(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 { log.Fatalf("Build URL: %v", err) } @@ -1323,7 +1323,7 @@ func listGroupsCmd(cmdname string, args []string) { var groups []string _, err = getJSON(u, &groups) if err != nil { - log.Fatalf("Get groups: %v", err) + log.Fatalf("Get halls: %v", err) } sort.Slice(groups, func(i, j int) bool { return groups[i] < groups[j] @@ -1343,13 +1343,13 @@ func listGroupsCmd(cmdname string, args []string) { } func listTokensCmd(cmdname string, args []string) { - var groupname stringOption + var hallname stringOption var long bool cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", 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.Parse(args) @@ -1358,14 +1358,14 @@ func listTokensCmd(cmdname string, args []string) { os.Exit(1) } - if !groupname.set { + if !hallname.set { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/", + serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/", ) if err != nil { @@ -1430,14 +1430,14 @@ func listTokensCmd(cmdname string, args []string) { } func createTokenCmd(cmdname string, args []string) { - var groupname stringOption + var hallname stringOption var username, permissions string var includeSubgroups boolOption cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.Var(&groupname, "group", "group `name`") + cmd.Var(&hallname, "hall", "hall `name`") cmd.Var(&includeSubgroups, "include-subgroups", "include subgroups") cmd.StringVar(&username, "user", "", "encode user `name` in token") cmd.StringVar(&permissions, "permissions", "present", "permissions") @@ -1448,9 +1448,9 @@ func createTokenCmd(cmdname string, args []string) { os.Exit(1) } - if !groupname.set { + if !hallname.set { fmt.Fprintf(cmd.Output(), - "Option \"-group\" is required\n") + "Option \"-hall\" is required\n") os.Exit(1) } @@ -1469,7 +1469,7 @@ func createTokenCmd(cmdname string, args []string) { } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups/", groupname.value, ".tokens/", + serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens/", ) if err != nil { log.Fatalf("Build URL: %v", err) @@ -1483,13 +1483,13 @@ func createTokenCmd(cmdname string, args []string) { } func revokeTokenCmd(cmdname string, args []string) { - var groupname stringOption + var hallname stringOption var token string cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.Var(&groupname, "group", "group `name`") + cmd.Var(&hallname, "hall", "hall `name`") cmd.StringVar(&token, "token", "", "`token` to delete") cmd.Parse(args) @@ -1498,14 +1498,14 @@ func revokeTokenCmd(cmdname string, args []string) { os.Exit(1) } - if !groupname.set || token == "" { + if !hallname.set || token == "" { fmt.Fprintf(cmd.Output(), - "Options \"-group\" and \"-token\" are required\n") + "Options \"-hall\" and \"-token\" are required\n") os.Exit(1) } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups/", groupname.value, + serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens", token, ) if err != nil { @@ -1522,13 +1522,13 @@ func revokeTokenCmd(cmdname string, args []string) { } func deleteTokenCmd(cmdname string, args []string) { - var groupname stringOption + var hallname stringOption var token string cmd := flag.NewFlagSet(cmdname, flag.ExitOnError) setUsage(cmd, cmdname, "%v [option...] %v [option...]\n", os.Args[0], cmdname, ) - cmd.Var(&groupname, "group", "group `name`") + cmd.Var(&hallname, "hall", "hall `name`") cmd.StringVar(&token, "token", "", "`token` to delete") cmd.Parse(args) @@ -1537,14 +1537,14 @@ func deleteTokenCmd(cmdname string, args []string) { os.Exit(1) } - if !groupname.set || token == "" { + if !hallname.set || token == "" { fmt.Fprintf(cmd.Output(), - "Options \"-group\" and \"-token\" are required\n") + "Options \"-hall\" and \"-token\" are required\n") os.Exit(1) } u, err := url.JoinPath( - serverURL, "/skald-api/v0/.groups/", groupname.value, + serverURL, "/skald-api/v0/.halls/", hallname.value, ".tokens", token, ) if err != nil { diff --git a/static/example/example.js b/static/example/example.js index efe46d1..5205b2c 100644 --- a/static/example/example.js +++ b/static/example/example.js @@ -307,7 +307,7 @@ document.getElementById('start').onclick = async function(e) { let button = /** @type{HTMLButtonElement} */(this); button.hidden = true; try { - await start("/group/public/"); + await start("/hall/public/"); } catch(e) { displayError(e); }; diff --git a/static/index.html b/static/index.html index 6a051d9..dd43049 100644 --- a/static/index.html +++ b/static/index.html @@ -15,22 +15,22 @@

Skald

-
- - + + +

-
-

Public groups

+
+

Public halls

-
+
diff --git a/static/mainpage.css b/static/mainpage.css index d94bc7b..94dfe31 100644 --- a/static/mainpage.css +++ b/static/mainpage.css @@ -26,7 +26,7 @@ body { padding: 1.875rem; } -#public-groups-table tr a{ +#public-halls-table tr a{ margin-left: 0.9375rem; font-weight: 700; } diff --git a/static/mainpage.js b/static/mainpage.js index 8488377..428e38d 100644 --- a/static/mainpage.js +++ b/static/mainpage.js @@ -20,16 +20,16 @@ 'use strict'; -document.getElementById('groupform').onsubmit = async function(e) { +document.getElementById('hallform').onsubmit = async function(e) { e.preventDefault(); clearError(); - let groupinput = document.getElementById('group') + let groupinput = document.getElementById('hall') let button = document.getElementById('submitbutton'); let group = groupinput.value.trim(); if(group === '') return; - let url = '/group/' + group + '/'; + let url = '/hall/' + group + '/'; let statusUrl = url + '.status.json'; try { @@ -78,25 +78,25 @@ function clearError() { } } -async function listPublicGroups() { - let div = document.getElementById('public-groups'); - let table = document.getElementById('public-groups-table'); +async function listPublicHalls() { + let div = document.getElementById('public-halls'); + let table = document.getElementById('public-halls-table'); let l; try { - let r = await fetch('/public-groups.json'); + let r = await fetch('/public-halls.json'); if(!r.ok) throw new Error(`${r.status} ${r.statusText}`); l = await r.json(); } catch(e) { - table.textContent = `Couldn't fetch groups: ${e}`; + table.textContent = `Couldn't fetch halls: ${e}`; div.classList.remove('nogroups'); div.classList.add('groups'); return; } if (l.length === 0) { - table.textContent = '(No groups found.)'; + table.textContent = '(No halls found.)'; div.classList.remove('groups'); div.classList.add('nogroups'); return; @@ -131,4 +131,4 @@ async function listPublicGroups() { } -listPublicGroups(); +listPublicHalls(); diff --git a/static/management.js b/static/management.js index 8cd11d7..d753f25 100644 --- a/static/management.js +++ b/static/management.js @@ -159,7 +159,7 @@ async function updateObject(url, values, etag) { * @returns {Promise>} */ 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} */ 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] */ 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] */ 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 * are left unchanged. * @@ -203,7 +203,7 @@ async function deleteGroup(group, etag) { * @param {string} [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>} */ 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) { if(wildcard) - return `/skald-api/v0/.groups/${group}/.wildcard-user`; + return `/skald-api/v0/.halls/${group}/.wildcard-user`; else if(user === "") - return `/skald-api/v0/.groups/${group}/.empty-user`; + return `/skald-api/v0/.halls/${group}/.empty-user`; 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>} */ 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} */ 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); } @@ -353,7 +353,7 @@ async function createToken(group, template) { } let r = await fetch( - `/skald-api/v0/.groups/${group}/.tokens/`, + `/skald-api/v0/.halls/${group}/.tokens/`, options); if(!r.ok) throw httpError(r); @@ -373,6 +373,6 @@ async function updateToken(group, token, etag) { if(!token.token) throw new Error("Unnamed token"); return await updateObject( - `/skald-api/v0/.groups/${group}/.tokens/${token.token}`, + `/skald-api/v0/.halls/${group}/.tokens/${token.token}`, token, etag); } diff --git a/todo.txt b/todo.txt index 98bcd26..ecbbd84 100644 --- a/todo.txt +++ b/todo.txt @@ -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 # ============================================================================ -- Rename browser hall URLs from /group/name/ to /hall/name/ -- Rename /public-groups.json to /public-halls.json -- Rename /galene-api/ to /skald-api/ -- Rename API collection paths from .groups to .halls +X Rename browser hall URLs from /group/name/ to /hall/name/ +X Rename /public-groups.json to /public-halls.json +X Rename /galene-api/ to /skald-api/ +X Rename API collection paths from .groups to .halls - Rename protocol join fields from group to hall - 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/management.js to use /skald-api/v0/.halls/ -- Update static/mainpage.js to open /hall/ URLs and fetch /public-halls.json +X Update static/management.js to use /skald-api/v0/.halls/ +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 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 help text and error messages -- Update API and webserver tests for /hall/ and /skald-api/ +X Update skaldctl help text and error messages +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 protocol backward compatibility with Skald is intentionally broken diff --git a/token/jwt.go b/token/jwt.go index fd6db4a..a29b9e7 100644 --- a/token/jwt.go +++ b/token/jwt.go @@ -190,10 +190,10 @@ func parseJWT(token string, keys []map[string]any) (*JWT, error) { func matchGroup(pth, group string, includeSubgroups bool) bool { if !includeSubgroups { - return pth == "/group/"+group+"/" + return pth == "/hall/"+group+"/" } - if !strings.HasPrefix(pth, "/group/") { + if !strings.HasPrefix(pth, "/hall/") { return false } @@ -201,7 +201,7 @@ func matchGroup(pth, group string, includeSubgroups bool) bool { 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) { diff --git a/token/jwt_test.go b/token/jwt_test.go index c040a67..b83151f 100644 --- a/token/jwt_test.go +++ b/token/jwt_test.go @@ -85,21 +85,21 @@ func TestMatchGroup(t *testing.T) { s bool } good := []tt{ - {"/group/a/", "a", false}, - {"/group/a/b/", "a/b", false}, - {"/group/a/", "a", true}, - {"/group/a/", "a/b", true}, - {"/group/a/b/", "a/b", true}, - {"/group/a/b/", "a/b/c", true}, + {"/hall/a/", "a", false}, + {"/hall/a/b/", "a/b", false}, + {"/hall/a/", "a", true}, + {"/hall/a/", "a/b", true}, + {"/hall/a/b/", "a/b", true}, + {"/hall/a/b/", "a/b/c", true}, } bad := []tt{ - {"/group/a/", "b", false}, - {"/group/a", "a", false}, - {"/group/a", "a", true}, - {"/group/a/", "a/b", false}, - {"/group/a/b/", "a", false}, - {"/group/a/b/", "a", true}, + {"/hall/a/", "b", false}, + {"/hall/a", "a", false}, + {"/hall/a", "a", true}, + {"/hall/a/", "a/b", false}, + {"/hall/a/b/", "a", false}, + {"/hall/a/b/", "a", true}, } for _, test := range good { @@ -142,7 +142,7 @@ func TestJWT(t *testing.T) { john := "john" jack := "jack" baseClaims := jwt.MapClaims{ - "aud": "https://skald.org:8443/group/auth/", + "aud": "https://skald.org:8443/hall/auth/", "permissions": []string{"present"}, "iat": int64(1645310294), "exp": int64(2906750294), diff --git a/token/stateful_test.go b/token/stateful_test.go index eeaae35..dda1b2c 100644 --- a/token/stateful_test.go +++ b/token/stateful_test.go @@ -46,27 +46,27 @@ func TestStatefulCheck(t *testing.T) { user2 := "user2" token1 := &Stateful{ Token: "token", - Group: "group", + Group: "hall", Username: &user, Permissions: []string{"present", "message"}, Expires: &future, } token2 := &Stateful{ Token: "token", - Group: "group", + Group: "hall", Permissions: []string{"present", "message"}, Expires: &future, } token3 := &Stateful{ Token: "token", - Group: "group", + Group: "hall", Username: &user, Permissions: []string{"present", "message"}, Expires: &past, } token4 := &Stateful{ Token: "token", - Group: "group", + Group: "hall", Username: &user, Permissions: []string{"present", "message"}, Expires: &future, @@ -74,7 +74,7 @@ func TestStatefulCheck(t *testing.T) { } token5 := &Stateful{ Token: "token", - Group: "group", + Group: "hall", IncludeSubgroups: true, Username: &user, Permissions: []string{"present", "message"}, @@ -106,48 +106,48 @@ func TestStatefulCheck(t *testing.T) { }{ { token: token1, - group: "group", + group: "hall", username: &user, expUsername: user, expPermissions: []string{"present", "message"}, }, { token: token1, - group: "group", + group: "hall", username: &user2, expUsername: user, expPermissions: []string{"present", "message"}, }, { token: token1, - group: "group", + group: "hall", expUsername: user, expPermissions: []string{"present", "message"}, }, { token: token2, - group: "group", + group: "hall", username: &user, expUsername: "", expPermissions: []string{"present", "message"}, }, { token: token5, - group: "group", + group: "hall", username: &user, expUsername: "user", expPermissions: []string{"present", "message"}, }, { token: token5, - group: "group/subgroup", + group: "hall/subhall", username: &user, expUsername: "user", expPermissions: []string{"present", "message"}, }, { token: token7, - group: "group/subgroup", + group: "hall/subhall", username: &user, expUsername: "user", expPermissions: []string{"present", "message"}, @@ -171,17 +171,17 @@ func TestStatefulCheck(t *testing.T) { }{ { token: token1, - group: "group2", + group: "hall2", username: &user, }, { token: token3, - group: "group", + group: "hall", username: &user, }, { token: token4, - group: "group", + group: "hall", username: &user, }, { @@ -196,7 +196,7 @@ func TestStatefulCheck(t *testing.T) { }, { token: token6, - group: "group", + group: "hall", username: &user, }, { diff --git a/token/token_test.go b/token/token_test.go index 816dbf7..f68a6d3 100644 --- a/token/token_test.go +++ b/token/token_test.go @@ -25,7 +25,7 @@ func TestToken(t *testing.T) { user := "user" _, err = Update(&Stateful{ Token: "token", - Group: "group", + Group: "hall", Username: &user, Permissions: []string{"present"}, Expires: &future, @@ -39,7 +39,7 @@ func TestToken(t *testing.T) { t.Fatalf("Parse: %v", err) } - _, _, err = token.Check("skald.org:8443", "group", &user) + _, _, err = token.Check("skald.org:8443", "hall", &user) if err != nil { t.Errorf("Check: %v", err) } diff --git a/webserver/api.go b/webserver/api.go index 6b2bd73..ab6da32 100644 --- a/webserver/api.go +++ b/webserver/api.go @@ -34,7 +34,7 @@ func checkAdmin(w http.ResponseWriter, r *http.Request) bool { // checkPasswordAdmin checks whether the client authentifies as either an // administrator or the given user. It is used to check whether the // 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() if ok { ok, err := adminMatch(username, password) @@ -47,11 +47,11 @@ func checkPasswordAdmin(w http.ResponseWriter, r *http.Request, groupname, user } } if ok && !wildcard && username == user { - desc, err := group.GetDescription(groupname) + desc, err := group.GetDescription(hallname) if err != nil { internalError(w, "Get description for group %v: %v", - groupname, err, + hallname, err, ) return false } @@ -169,7 +169,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("cache-control", "no-cache") sendJSON(w, r, stats.GetGroups()) - case ".groups": + case ".halls": apiGroupHandler(w, r, rest) default: http.NotFound(w, r) diff --git a/webserver/api_test.go b/webserver/api_test.go index 1490156..d635529 100644 --- a/webserver/api_test.go +++ b/webserver/api_test.go @@ -115,58 +115,58 @@ func TestApi(t *testing.T) { } var groups []string - err = getJSON("/skald-api/v0/.groups/", &groups) + err = getJSON("/skald-api/v0/.halls/", &groups) 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\"", "", "{}") 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", "", "", "Hello, world!") 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) } - resp, err = do("PUT", "/skald-api/v0/.groups/test/", + resp, err = do("PUT", "/skald-api/v0/.halls/test/", "application/json", "", "*", "{}") 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 - err = getJSON("/skald-api/v0/.groups/test/", &desc) + err = getJSON("/skald-api/v0/.halls/test/", &desc) 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", "", "*", "{}") 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 { - 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" { - 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", "", "", `{"keys": [{ "kty": "oct", "alg": "HS256", @@ -176,12 +176,12 @@ func TestApi(t *testing.T) { 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 { 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", "", "*", `hello, world!`) if err != nil || resp.StatusCode != http.StatusUnsupportedMediaType { @@ -189,7 +189,7 @@ func TestApi(t *testing.T) { 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", "", "*", `{"permissions": "present"}`) if err != nil || resp.StatusCode != http.StatusCreated { @@ -197,26 +197,26 @@ func TestApi(t *testing.T) { } 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" { 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", "", "*", `{"permissions": "present"}`) if err != nil || resp.StatusCode != http.StatusPreconditionFailed { 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", "", "", `"toto"`) if err != nil || resp.StatusCode != http.StatusNoContent { 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", "", "", `toto`) if err != nil || resp.StatusCode != http.StatusNoContent { @@ -224,7 +224,7 @@ func TestApi(t *testing.T) { } 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 { 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) } - 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 { - t.Errorf("Delete group: %v %v", err, resp.StatusCode) + t.Errorf("Delete hall: %v %v", err, resp.StatusCode) } desc, err = group.GetDescription("test") @@ -260,14 +260,14 @@ func TestApi(t *testing.T) { 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", "", "*", `{"permissions": "present"}`) if err != nil || resp.StatusCode != http.StatusCreated { 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 { 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) } - 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 { 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)) } - resp, err = do("POST", "/skald-api/v0/.groups/test/.tokens/", + resp, err = do("POST", "/skald-api/v0/.halls/test/.tokens/", "application/json", "", "", `{"group":"bad"}`) if err != nil || resp.StatusCode != http.StatusBadRequest { 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", "", "", "{}") if err != nil || resp.StatusCode != http.StatusCreated { 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}") if err != nil || resp.StatusCode != http.StatusCreated { t.Errorf("Create global token: %v %v", err, resp.StatusCode) } 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 { t.Errorf("Get tokens: %v %v", err, toknames) } tokname := toknames[0] 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 { 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) } - tokenpath := "/skald-api/v0/.groups/test/.tokens/" + tokname + tokenpath := "/skald-api/v0/.halls/test/.tokens/" + tokname var tok token.Stateful err = getJSON(tokenpath, &tok) if err != nil { @@ -375,13 +375,13 @@ func TestApi(t *testing.T) { 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", "", "*", "{}") if err != nil || resp.StatusCode != http.StatusCreated { 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, "", "", "", "") if err != nil || resp.StatusCode != http.StatusNotFound { 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) } - 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 { 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 { - t.Errorf("Delete group: %v %v", err, resp.StatusCode) + t.Errorf("Delete hall: %v %v", err, resp.StatusCode) } _, err = group.GetDescription("test") @@ -448,8 +448,8 @@ func TestApiBadAuth(t *testing.T) { } do("GET", "/skald-api/v0/.stats") - do("GET", "/skald-api/v0/.groups/") - do("PUT", "/skald-api/v0/.groups/test/") + do("GET", "/skald-api/v0/.halls/") + do("PUT", "/skald-api/v0/.halls/test/") f, err := os.Create(filepath.Join(group.Directory, "test.json")) if err != nil { @@ -460,20 +460,20 @@ func TestApiBadAuth(t *testing.T) { }\n`) f.Close() - do("PUT", "/skald-api/v0/.groups/test/") - do("DELETE", "/skald-api/v0/.groups/test/") - do("GET", "/skald-api/v0/.groups/test/.users/") - do("GET", "/skald-api/v0/.groups/test/.users/jch") - do("GET", "/skald-api/v0/.groups/test/.users/jch") - do("PUT", "/skald-api/v0/.groups/test/.users/jch") - do("DELETE", "/skald-api/v0/.groups/test/.users/jch") - do("GET", "/skald-api/v0/.groups/test/.users/not-jch") - do("PUT", "/skald-api/v0/.groups/test/.users/not-jch") - do("PUT", "/skald-api/v0/.groups/test/.users/jch/.password") - do("POST", "/skald-api/v0/.groups/test/.users/jch/.password") - do("GET", "/skald-api/v0/.groups/test/.tokens/") - do("POST", "/skald-api/v0/.groups/test/.tokens/") - do("GET", "/skald-api/v0/.groups/test/.tokens/token") - do("PUT", "/skald-api/v0/.groups/test/.tokens/token") - do("DELETE", "/skald-api/v0/.groups/test/.tokens/token") + do("PUT", "/skald-api/v0/.halls/test/") + do("DELETE", "/skald-api/v0/.halls/test/") + do("GET", "/skald-api/v0/.halls/test/.users/") + do("GET", "/skald-api/v0/.halls/test/.users/jch") + do("GET", "/skald-api/v0/.halls/test/.users/jch") + do("PUT", "/skald-api/v0/.halls/test/.users/jch") + do("DELETE", "/skald-api/v0/.halls/test/.users/jch") + do("GET", "/skald-api/v0/.halls/test/.users/not-jch") + do("PUT", "/skald-api/v0/.halls/test/.users/not-jch") + do("PUT", "/skald-api/v0/.halls/test/.users/jch/.password") + do("POST", "/skald-api/v0/.halls/test/.users/jch/.password") + do("GET", "/skald-api/v0/.halls/test/.tokens/") + do("POST", "/skald-api/v0/.halls/test/.tokens/") + do("GET", "/skald-api/v0/.halls/test/.tokens/token") + do("PUT", "/skald-api/v0/.halls/test/.tokens/token") + do("DELETE", "/skald-api/v0/.halls/test/.tokens/token") } diff --git a/webserver/webserver.go b/webserver/webserver.go index 8f0753e..7db26ed 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -35,7 +35,7 @@ var Insecure bool func Serve(address string, dataDir string) error { http.Handle("/", &fileHandler{http.Dir(StaticRoot)}) - http.HandleFunc("/group/", groupHandler) + http.HandleFunc("/hall/", groupHandler) http.HandleFunc("/recordings", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, @@ -43,7 +43,7 @@ func Serve(address string, dataDir string) error { }) http.HandleFunc("/recordings/", recordingsHandler) http.HandleFunc("/ws", wsHandler) - http.HandleFunc("/public-groups.json", publicHandler) + http.HandleFunc("/public-halls.json", publicHandler) http.HandleFunc("/skald-api/", apiHandler) s := &http.Server{ @@ -346,7 +346,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/group/", r.URL.Path) + name := parseGroupName("/hall/", r.URL.Path) if name == "" { notFound(w) return @@ -358,8 +358,8 @@ func groupHandler(w http.ResponseWriter, r *http.Request) { return } - if r.URL.Path != "/group/"+name+"/" { - http.Redirect(w, r, "/group/"+name+"/", + if r.URL.Path != "/hall/"+name+"/" { + http.Redirect(w, r, "/hall/"+name+"/", http.StatusPermanentRedirect) return } @@ -415,7 +415,7 @@ func groupStatusHandler(w http.ResponseWriter, r *http.Request) { internalError(w, "groupStatusHandler: this shouldn't happen") return } - name := parseGroupName("/group/", pth) + name := parseGroupName("/hall/", pth) if name == "" { notFound(w) 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() if !ok { return false } - g := group.Get(groupname) + g := group.Get(hallname) if g == nil { return false } diff --git a/webserver/webserver_test.go b/webserver/webserver_test.go index aa0a6f0..b400135 100644 --- a/webserver/webserver_test.go +++ b/webserver/webserver_test.go @@ -19,17 +19,17 @@ func TestParseGroupName(t *testing.T) { {"/foo", ""}, {"foo", ""}, {"group/foo", ""}, - {"/group", ""}, - {"/group/..", ""}, - {"/group/foo/../bar", "bar"}, - {"/group/foo", "foo"}, - {"/group/foo/", "foo"}, - {"/group/foo/bar", "foo/bar"}, - {"/group/foo/bar/", "foo/bar"}, + {"/hall", ""}, + {"/hall/..", ""}, + {"/hall/foo/../bar", "bar"}, + {"/hall/foo", "foo"}, + {"/hall/foo/", "foo"}, + {"/hall/foo/bar", "foo/bar"}, + {"/hall/foo/bar/", "foo/bar"}, } for _, pg := range a { - g := parseGroupName("/group/", pg.p) + g := parseGroupName("/hall/", pg.p) if g != pg.g { t.Errorf("Path %v, got %v, expected %v", pg.p, g, pg.g) diff --git a/webserver/whip.go b/webserver/whip.go index 276de26..6a6e204 100644 --- a/webserver/whip.go +++ b/webserver/whip.go @@ -153,7 +153,7 @@ func whipEndpointHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/group/", pth) + name := parseGroupName("/hall/", pth) if name == "" { notFound(w) return @@ -269,7 +269,7 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) { return } - name := parseGroupName("/group/", pth) + name := parseGroupName("/hall/", pth) if name == "" { notFound(w) return