forked from Telodendria/Telodendria
Merge branch 'master' into setup-ci-workflows
Some checks failed
Testing pushed commits / compile-all (push) Has been cancelled
Some checks failed
Testing pushed commits / compile-all (push) Has been cancelled
This commit is contained in:
commit
38dfb2a505
51 changed files with 938 additions and 1106 deletions
25
README.md
25
README.md
|
@ -1,7 +1,6 @@
|
|||
<p align="center"><img src="https://telodendria.io/assets/Telodendria-500x500.png"></p>
|
||||
<h1 align="center">Telodendria</h1>
|
||||
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
||||
|
||||
Telodendria is an extremely powerful, yet lightweight and portable
|
||||
**Telodendria** is an extremely powerful, yet lightweight and portable
|
||||
chat server designed to be easy to install and configure. Powered by
|
||||
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
||||
everyone to run their own chat server on ordinary hardware, including
|
||||
|
@ -12,7 +11,7 @@ hosting a complicated, high-maintenance homeserver or joining an
|
|||
existing homeserver for privacy or other reasons, then Telodendria
|
||||
might be for you.
|
||||
|
||||
> **Note:** Telodendria still in development. See [Status](#status).
|
||||
!!!! **Note:** Telodendria still in development. See **Status** below.
|
||||
|
||||
## What is Matrix?
|
||||
|
||||
|
@ -64,12 +63,13 @@ incredibly outdated. Telodendria, on the other hand, aims to be stable.
|
|||
It should *just work* for long periods of time between upgrades, and
|
||||
you should never feel like Telodendria is going to change significantly
|
||||
between upgrades.
|
||||
- **Well-Documented:** Telodendria places as much emphasis on documentation as on code, which means you can be sure that the documentation will always remain up-to-date, accurate, and most importantly, reasonably exhaustive.
|
||||
|
||||
[Read Technical Rationale →](docs/dev/rationale.md)
|
||||
[Read Technical Rationale →](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
|
||||
|
||||
## Get Started
|
||||
|
||||
Check out the [Documentation](docs/README.md) to get started with
|
||||
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
|
||||
Telodendria.
|
||||
|
||||
## Status
|
||||
|
@ -79,13 +79,13 @@ not yet deliver on all of its promises. Currently, Telodendria is not
|
|||
ready for end-users yet. While it features very basic user
|
||||
authentication, it does not actually work as a chat server yet.
|
||||
|
||||
We are hoping to ship Telodendria `v0.4.0` by May of 2024. This
|
||||
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
|
||||
release should be usable for communication between **local users**
|
||||
only. Additional features, including federation with other Matrix
|
||||
homeservers will be added in future releases.
|
||||
|
||||
You can help speed up development by [sponsoring](#sponsorship)
|
||||
Telodendria or [getting involved](docs/CONTRIBUTING.md).
|
||||
You can help speed up development by **sponsoring**
|
||||
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
|
||||
|
||||
## Sponsorship
|
||||
|
||||
|
@ -96,7 +96,7 @@ Telodendria's long-term success, please consider sponsoring the
|
|||
project.
|
||||
|
||||
You can make a recurring donation to Telodendria using
|
||||
[LiberaPay](https://bancino.net/Telodendria/donate). You can also make
|
||||
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
|
||||
one-time donations using
|
||||
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
||||
like to make a recurring donation larger than that allowed by
|
||||
|
@ -108,7 +108,7 @@ LiberaPay, please contact Jordan Bancino over Matrix at
|
|||
While there are no set sponsorship tiers at this time, sponsoring
|
||||
Telodendria is a mutually beneficial relationship. Depending on the
|
||||
amount you donate, you can get your name, logo, and website links
|
||||
on the [Sponsors](docs/SPONSORS.md) page, the project `README`, or the
|
||||
on the [Sponsors](../sponsors) page, the project `README`, or the
|
||||
main website.
|
||||
|
||||
## License
|
||||
|
@ -116,7 +116,7 @@ main website.
|
|||
All of the code and documentation for Telodendria is licensed under a
|
||||
modified MIT license. The MIT license is an extremely permissive
|
||||
license that has very few restrictions. Please consult the
|
||||
[`LICENSE.txt`](LICENSE.txt) file for the actual license text. It is
|
||||
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
|
||||
important to note that the Telodendria license text differs from the
|
||||
original MIT license in the following ways:
|
||||
|
||||
|
@ -133,4 +133,3 @@ to use the logo in any way as long as it represents or links to the
|
|||
official project. If Telodendria is forked, the logo must be removed
|
||||
completely from the project, and optionally replaced by a different
|
||||
one.
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
|
||||
"header": "Schema\/Filter.h",
|
||||
"types": {
|
||||
"FilterRoom": {
|
||||
|
@ -116,6 +117,5 @@
|
|||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H"
|
||||
}
|
||||
}
|
||||
|
|
38
Schema/LoginRequest.json
Normal file
38
Schema/LoginRequest.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"header": "Schema\/LoginRequest.h",
|
||||
"types": {
|
||||
"LoginRequestType": {
|
||||
"fields": {
|
||||
"m.login.password": { "name": "REQUEST_TYPE_PASSWORD" }
|
||||
},
|
||||
"type": "enum"
|
||||
},
|
||||
"LoginRequestUserIdentifier": {
|
||||
"fields": {
|
||||
"type": { "type": "string" },
|
||||
"user": { "type": "string" }
|
||||
},
|
||||
"type": "struct"
|
||||
},
|
||||
"LoginRequest": {
|
||||
"fields": {
|
||||
"type": { "type": "LoginRequestType" },
|
||||
|
||||
"identifier": { "type": "object" },
|
||||
|
||||
"password": { "type": "string" },
|
||||
"address": { "type": "string" },
|
||||
"user": { "type": "string" },
|
||||
"device_id": { "type": "string" },
|
||||
"initial_device_display_name": { "type": "string" },
|
||||
"medium": { "type": "string" },
|
||||
"token": { "type": "string" },
|
||||
|
||||
"refresh_token": { "type": "boolean" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_LOGIN_REQUEST_H"
|
||||
}
|
||||
|
49
Schema/RegToken.json
Normal file
49
Schema/RegToken.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"guard": "TELODENDRIA_SCHEMA_REGTOKEN_H",
|
||||
"header": "Schema\/RegToken.h",
|
||||
"include": [
|
||||
"Cytoplasm\/Db.h"
|
||||
],
|
||||
"types": {
|
||||
"Db *": {
|
||||
"type": "extern"
|
||||
},
|
||||
"DbRef *": {
|
||||
"type": "extern"
|
||||
},
|
||||
"RegTokenInfo": {
|
||||
"fields": {
|
||||
"db": {
|
||||
"type": "Db *",
|
||||
"ignore": true
|
||||
},
|
||||
"ref": {
|
||||
"type": "DbRef *",
|
||||
"ignore": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_on": {
|
||||
"type": "integer"
|
||||
},
|
||||
"expires_on": {
|
||||
"type": "integer"
|
||||
},
|
||||
"used": {
|
||||
"type": "integer"
|
||||
},
|
||||
"uses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"grants": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
}
|
||||
}
|
17
Schema/Registration.json
Normal file
17
Schema/Registration.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"header": "Schema\/Registration.h",
|
||||
"types": {
|
||||
"RegistrationRequest": {
|
||||
"fields": {
|
||||
"username": { "type": "string" },
|
||||
"password": { "type": "string" },
|
||||
"device_id": { "type": "string" },
|
||||
"inhibit_login": { "type": "boolean" },
|
||||
"initial_device_display_name": { "type": "string" },
|
||||
"refresh_token": { "type": "boolean" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_REGISTRATION_H"
|
||||
}
|
21
Schema/RequestToken.json
Normal file
21
Schema/RequestToken.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"header": "Schema\/RequestToken.h",
|
||||
"types": {
|
||||
"RequestToken": {
|
||||
"fields": {
|
||||
"client_secret": { "type": "string" },
|
||||
"send_attempt": { "type": "integer" },
|
||||
"next_link": { "type": "string" },
|
||||
"id_access_token": { "type": "string" },
|
||||
"id_server": { "type": "string" },
|
||||
|
||||
"email": { "type": "string" },
|
||||
|
||||
"country": { "type": "string" },
|
||||
"phone_number": { "type": "string" }
|
||||
},
|
||||
"type": "struct"
|
||||
}
|
||||
},
|
||||
"guard": "TELODENDRIA_SCHEMA_REQUESTTOKEN_H"
|
||||
}
|
41
configure
vendored
41
configure
vendored
|
@ -14,12 +14,12 @@ INCLUDE="src/include"
|
|||
TOOLS="tools/src"
|
||||
SCHEMA="Schema"
|
||||
|
||||
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE}"
|
||||
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
||||
LIBS="-lm -pthread -lCytoplasm"
|
||||
|
||||
|
||||
# Set default args for all platforms
|
||||
SCRIPT_ARGS="--prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=0.4.0 --static $@"
|
||||
SCRIPT_ARGS="--cc=cc --prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=1.7.0-alpha4 --static $@"
|
||||
|
||||
echo "Processing options..."
|
||||
echo "Ran with arguments: $SCRIPT_ARGS"
|
||||
|
@ -27,6 +27,9 @@ echo "Ran with arguments: $SCRIPT_ARGS"
|
|||
# Process all arguments
|
||||
for arg in $SCRIPT_ARGS; do
|
||||
case "$arg" in
|
||||
--cc=*)
|
||||
CC=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
--prefix=*)
|
||||
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
||||
;;
|
||||
|
@ -60,7 +63,7 @@ for arg in $SCRIPT_ARGS; do
|
|||
STATIC=""
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument: $1"
|
||||
echo "Invalid argument: $arg"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
@ -112,8 +115,8 @@ compile_obj() {
|
|||
src="$1"
|
||||
obj="$2"
|
||||
|
||||
pref=$(cc -I${INCLUDE} -MM -MT "${obj}" "${src}")
|
||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${INCLUDE}/Schema/ print_obj)"
|
||||
pref=$(${CC} -I${INCLUDE} -I${BUILD} -MM -MT "${obj}" "${src}")
|
||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
|
||||
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
||||
}
|
||||
|
@ -153,13 +156,19 @@ compile_schema() {
|
|||
src="$1"
|
||||
out="$2"
|
||||
|
||||
echo "${INCLUDE}/Schema/${out}.h:"
|
||||
echo "${TAB}@mkdir -p ${INCLUDE}/Schema ${SRC}/Schema"
|
||||
echo "${TAB}j2s -s \"${src}\" -h \"${INCLUDE}/Schema/${out}.h\" -c \"${SRC}/Schema/${out}.c\""
|
||||
obj="${BUILD}/Schema/${out}.o"
|
||||
|
||||
echo "${SRC}/Schema/${out}.c:"
|
||||
echo "${TAB}@mkdir -p ${INCLUDE}/Schema ${SRC}/Schema"
|
||||
echo "${TAB}j2s -s \"${src}\" -h \"${INCLUDE}/Schema/${out}.h\" -c \"${SRC}/Schema/${out}.c\""
|
||||
echo "${BUILD}/Schema/${out}.h:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}j2s -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${BUILD}/Schema/${out}.c:"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}j2s -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
||||
|
||||
echo "${obj}: ${src} ${BUILD}/Schema/${out}.c"
|
||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${BUILD}/Schema/${out}.c\""
|
||||
}
|
||||
|
||||
install_out() {
|
||||
|
@ -185,22 +194,16 @@ uninstall_out() {
|
|||
|
||||
echo "Generating Makefile..."
|
||||
|
||||
OBJS=$(collect ${SRC}/ .c .o ${BUILD}/ print_obj)
|
||||
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
||||
TAB=$(printf '\t')
|
||||
|
||||
# If objects don't include the schema (this is the first configure),
|
||||
# then include them manually.
|
||||
if ! echo "${OBJS}" | grep "Schema" > /dev/null; then
|
||||
OBJS="${OBJS} $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
||||
fi
|
||||
|
||||
cat << EOF > Makefile
|
||||
.POSIX:
|
||||
|
||||
# Generated by '$0' on $(date).
|
||||
# This file should generally not be manually edited.
|
||||
|
||||
CC = cc
|
||||
CC = ${CC}
|
||||
PREFIX = ${PREFIX}
|
||||
CFLAGS = ${CFLAGS}
|
||||
LDFLAGS = ${LDFLAGS}
|
||||
|
|
|
@ -5,12 +5,22 @@ It is intended to be updated with every commit that makes a user-facing change w
|
|||
reporting in the change log. As such, it changes frequently between releases. Final
|
||||
change log entries are published as [Releases](releases).
|
||||
|
||||
## v0.4.0
|
||||
## v1.7.0-alpha4
|
||||
|
||||
**Not Released Yet.**
|
||||
|
||||
This release brings filters, rooms, and events! The core of the Matrix protocol architecture
|
||||
is not in place.
|
||||
This release brings filters, rooms, and events! The core of the Matrix
|
||||
protocol architecture is now in place.
|
||||
|
||||
Note that the versioning scheme has changed from `v0.X.0` to
|
||||
`v1.7.0-alphaX`. This is so that Telodendria releases correspond to the
|
||||
Matrix specification that they implement, in accordance with
|
||||
[this blog post](https://telodendria.io/blog/on-matrixs-release-cadence-and-state-resolution-v1).
|
||||
This versioning scheme change does not indicate a drastic leap forward
|
||||
in Telodendria's development—the `-alpha4` suffix indicates that
|
||||
this is the 4th pre-release, with the target being a stable `v1.7.0`.
|
||||
Note also that we still have a *long* way to go before we reach that
|
||||
stable release.
|
||||
|
||||
### Matrix Specification
|
||||
|
||||
|
@ -21,19 +31,30 @@ The following endpoints were added:
|
|||
|
||||
### Bug Fixes & General Improvements
|
||||
|
||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors with certain
|
||||
Matrix clients. (#35)
|
||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors
|
||||
with certain Matrix clients. (#35)
|
||||
- Improved compatibility with NetBSD on various platforms.
|
||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository.
|
||||
- Use a `configure` script and `make` to build Telodendria instead of custom scripts.
|
||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
|
||||
will now be maintained separately and have its own releases as well.
|
||||
- Use a `configure` script and `make` to build Telodendria instead of
|
||||
custom scripts.
|
||||
- Greatly simplified some endpoint code by using Cytoplasm's `j2s` for
|
||||
parsing request bodies.
|
||||
|
||||
### New Features
|
||||
|
||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins to be able to
|
||||
deactivate users.
|
||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`, because later revisions
|
||||
of the administrator API may break clients, so we want a way to give those breaking revisions
|
||||
new endpoints.
|
||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
|
||||
because later revisions of the administrator API may break clients, so
|
||||
we want a way to give those breaking revisions new endpoints.
|
||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
|
||||
to be able to deactivate users.
|
||||
- Added a **PUT** option to `/_telodendria/admin/v1/config` that gives
|
||||
the ability to change only a subset of the configuration.
|
||||
- Implemented the following APIs for managing registration tokens:
|
||||
- **GET** `/_telodendria/admin/tokens`
|
||||
- **GET** `/_telodendria/admin/tokens/[token]`
|
||||
- **POST** `/_telodendria/admin/tokens`
|
||||
- **DELETE** `/_telodendria/admin/tokens/[token]`
|
||||
|
||||
## v0.3.0
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ request.
|
|||
- [Configuration](config.md)
|
||||
- [Server Statistics](stats.md)
|
||||
- [Process Control](proc.md)
|
||||
- [Registration Tokens](tokens.md)
|
||||
|
||||
## API Conventions
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ As mentioned in [Setup](../setup.md), Telodendria's configuration is
|
|||
intended to be managed via the configuration API. Consult the
|
||||
[Configuration](../config.md) document for a complete list of supported
|
||||
configuration options. This document simply describes the API used to
|
||||
update the configuration.
|
||||
update the configuration described in that document.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/config`
|
||||
### **GET** `/_telodendria/admin/v1/config`
|
||||
|
||||
Retrieve the current configuration.
|
||||
|
||||
|
@ -20,7 +20,7 @@ Retrieve the current configuration.
|
|||
|---------------|-------------|
|
||||
| 200 | The current configuration was successfully retrieved.|
|
||||
|
||||
### **POST** `/_telodendria/admin/config`
|
||||
### **POST** `/_telodendria/admin/v1/config`
|
||||
|
||||
Installs a new configuration. This endpoint validates the request body,
|
||||
ensuring it is a proper configuration, then it replaces the existing
|
||||
|
@ -40,3 +40,23 @@ configuration with the new one.
|
|||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||
|
||||
### **PUT** `/_telodendria/admin/v1/config`
|
||||
|
||||
Update the currently installed configuration instead of completely replacing it. This endpoint
|
||||
validates the request body, merges it on top of the current configuration, validates the resulting
|
||||
configuration, then updates it in the database. This is useful when only one or two properties
|
||||
in the configuration needs to be changed.
|
||||
|
||||
| Requires Token | Rate Limited |
|
||||
|----------------|--------------|
|
||||
| Yes | Yes |
|
||||
|
||||
| Response Code | Description |
|
||||
|---------------|-------------|
|
||||
| 200 | The new configuration was successfully installed.|
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `restart_required` | `Boolean` | Whether or not the process needs to be restarted to finish applying the configuration. If this is `true`, then the restart endpoint should be used at a convenient time to apply the configuration.
|
||||
|
|
|
@ -41,7 +41,7 @@ this privilege level.
|
|||
|
||||
The following API endpoints are implemented for managing privileges.
|
||||
|
||||
### **GET** `/_telodendria/admin/privileges/[localpart]`
|
||||
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Retrieve the permissions for a user. If the localpart is omitted, then
|
||||
retrieve the privileges for the user that owns the access token being
|
||||
|
@ -62,7 +62,7 @@ used. Note that the owner of the access token must have the
|
|||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **POST** `/_telodendria/admin/privileges/[localpart]`
|
||||
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by replacing the privileges array
|
||||
with the one specified in the request. Like the **GET** version of this
|
||||
|
@ -89,7 +89,7 @@ owns the access token.
|
|||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **PUT** `/_telodendria/admin/privileges/[localpart]`
|
||||
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by adding the privileges
|
||||
specified in the request to the users existing privileges.
|
||||
|
@ -114,7 +114,7 @@ specified in the request to the users existing privileges.
|
|||
|-------|------|-------------|
|
||||
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||
|
||||
### **DELETE** `/_telodendria/admin/privileges/[localpart]`
|
||||
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
|
||||
|
||||
Update the privileges of a local user by removing the privileges
|
||||
specified in the request from the user's existing privileges.
|
||||
|
|
|
@ -5,7 +5,7 @@ administrator to manage the Telodendria process itself.
|
|||
|
||||
## API Endpoints
|
||||
|
||||
### **POST** `/_telodendria/admin/restart`
|
||||
### **POST** `/_telodendria/admin/v1/restart`
|
||||
|
||||
Restart the Telodendria daemon cleanly. This endpoint will respond
|
||||
immediately after signaling to the daemon that it should be restarted
|
||||
|
@ -26,7 +26,7 @@ starts over.
|
|||
|
||||
On success, this endpoint simply returns an empty JSON object.
|
||||
|
||||
### **POST** `/_telodendria/admin/shutdown`
|
||||
### **POST** `/_telodendria/admin/v1/shutdown`
|
||||
|
||||
Shut down the Telodendria process cleanly. This endpoint will respond
|
||||
immediately after signalling to the daemon that it should be shut
|
||||
|
|
|
@ -5,7 +5,7 @@ information about how the server process is performing.
|
|||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/stats`
|
||||
### **GET** `/_telodendria/admin/v1/stats`
|
||||
|
||||
Retrieve basic statistics about the currently running Telodendria
|
||||
process.
|
||||
|
|
106
docs/user/admin/tokens.md
Normal file
106
docs/user/admin/tokens.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Administrator API: Registration Tokens
|
||||
|
||||
Telodendria implements registration tokens as specified by the Matrix
|
||||
specification. These tokens can be used for registration using the
|
||||
`m.login.registration_token` login type. This API provides a Telodendria
|
||||
administrator with a mechanism for generating and managing these tokens,
|
||||
which allows controlled registration on the homeserver.
|
||||
|
||||
It is generally safer than completely open registration to use
|
||||
registration tokens that either expire after a short period of time, or
|
||||
have a limited number of uses.
|
||||
|
||||
## Registration Token
|
||||
|
||||
A registration token is represented by the following `RegToken` JSON
|
||||
object:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The token identifier; what is used when registering. |
|
||||
| `created_by` | `String` | The localpart of the user that created this token. |
|
||||
| `created_on` | `Integer` | A timestamp of when the token was created. |
|
||||
| `expires_on` | `Integer` | An expiration stamp, or 0 if the token never expires. |
|
||||
| `used` | `Integer` | The number of times the token has been used. |
|
||||
| `uses` | `Integer` | The total number of allowed uses, or -1 for unlimited. |
|
||||
| `grants` | `[String]` | An array of privileges to grant users that register with this token as described in [Privileges](privileges.md). |
|
||||
|
||||
All endpoints in this API will operate on some variation of this
|
||||
structure. The remaining number of uses can be computed by performing
|
||||
the subtraction: `uses - used`. `used` should never be greater than
|
||||
`uses` or less than `0`.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "q34jgapo8uq34hg",
|
||||
"created_by": "admin",
|
||||
"created_on": 1699467640000,
|
||||
"expires_on": 0,
|
||||
"used": 3,
|
||||
"uses": 5
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/tokens`
|
||||
|
||||
Get a list of all registration tokens and information about them.
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `tokens` | `[RegToken]` | An array of registration tokens. |
|
||||
|
||||
### **GET** `/_telodendria/admin/v1/tokens/[name]`
|
||||
|
||||
Get information about the specified registration token.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
This endpoint returns a `RegToken` object that represents the server's
|
||||
record of the registration token.
|
||||
|
||||
### **POST** `/_telodendria/admin/v1/tokens`
|
||||
|
||||
Create a new registration token.
|
||||
|
||||
#### Request Format
|
||||
|
||||
This endpoint accepts a `RegToken` object, as described above. If no
|
||||
`name` is provided, one will be randomly generated. Note that the fields
|
||||
`created_by`, `created_on`, and `used` are ignored and set by the server
|
||||
when this request is made. All other fields may be set by the request
|
||||
body.
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
If the creation of the registration token was successful, a `RegToken`
|
||||
that represents the server's record of it is returned.
|
||||
|
||||
### **DELETE** `/_telodendria/admin/v1/tokens/[name]`
|
||||
|
||||
Delete the specified registration token. It will no longer be usable for
|
||||
the registration of users. Any users that have completed the
|
||||
`m.login.registration_token` step but have not yet created their account
|
||||
should still be able to do so until their user-interactive auth session
|
||||
expires.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `String` | The name of the token, as it would be used to register a user. |
|
||||
|
||||
#### 200 Response Format
|
||||
|
||||
On success, this endpoint returns an empty JSON object.
|
138
site/index.html
138
site/index.html
|
@ -1,138 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="author" content="Jordan Bancino">
|
||||
<meta name="description"
|
||||
content="Telodendria, a Matrix homeserver written in ANSI C.">
|
||||
|
||||
<meta property="og:title"
|
||||
content="Telodendria | A Matrix Homeserver written in ANSI C.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url"
|
||||
content="https://telodendria.io">
|
||||
<meta property="og:description"
|
||||
content="Telodendria, a Matrix homeserver written in ANSI C.">
|
||||
|
||||
<meta http-equiv="onion-location" content="http://cszrjvqbeim4dbbynucd4xucpfsips4alpxyxm5s3uh2itjxsnythhyd.onion">
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="assets/Telodendria-196x196.png">
|
||||
|
||||
<title>Telodendria | A Matrix Homeserver written in ANSI C.</title>
|
||||
</head>
|
||||
<body>
|
||||
<img id="logo" src="/assets/Telodendria-500x500.png" alt="Telodendria Logo"/>
|
||||
<h1 style="font-size: x-large;" id="telodendria">Telodendria</h1>
|
||||
<p>
|
||||
<b>Tel-ə-'den-drē-ə:</b> The terminal aborizations of an axon.
|
||||
</p>
|
||||
<p>
|
||||
<b>Telodendria</b> is an open source Matrix homeserver implementation written from
|
||||
scratch in ANSI C and designed to be lightweight and simple, yet
|
||||
functional.
|
||||
</p>
|
||||
<div class="msg-error">
|
||||
<b><i>Note:</i></b> <b>Telodendria</b> is under <i>heavy</i> development and is not
|
||||
yet ready for use.
|
||||
Please see the <a href="/man/man7/telodendria-changelog.7.html#PROJECT_STATUS">Project Status</a>
|
||||
for information about the project state, and use the links below to help fund development.
|
||||
</div>
|
||||
<h2 id="donate">Donate</h2>
|
||||
<p>
|
||||
If you would like to donate to this project, you can do so with the
|
||||
following links:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://liberapay.com/Telodendria/donate">LiberaPay</a> (recurring, account required)</li>
|
||||
<li><a href="https://donate.stripe.com/8wM29AfF5bRJc48eUU">Stripe</a> (one-time, no account required)</li>
|
||||
</ul>
|
||||
<p>
|
||||
If you would like to do a recurring donation larger than what's allowed
|
||||
by LiberaPay, please contact me directly on Matrix at <code>@jordan:bancino.net</code>.
|
||||
</p>
|
||||
<h2 id="download">Download</h2>
|
||||
<p>
|
||||
<b>Telodendria</b> is distributed as source tarballs, in true Unix
|
||||
fashion. If you want, you can verify the checksum of your download,
|
||||
and check the signature. To check the signature, you'll need
|
||||
<code>signify</code>, and the signify public key:
|
||||
<a href="/telodendria-signify.pub">
|
||||
telodendria-signify.pub</a>.
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Download</th>
|
||||
<th>Checksum</th>
|
||||
<th>Signature</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${TELODENDRIA_VERSION}</td>
|
||||
<td>
|
||||
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.tar.gz">
|
||||
<code>Telodendria-v${TELODENDRIA_VERSION}.tar.gz</code>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.tar.gz.sha256">
|
||||
sha256
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/pub/v${TELODENDRIA_VERSION}/Telodendria-v${TELODENDRIA_VERSION}.tar.gz.sig">
|
||||
signify
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
See the <a href="man/man7/telodendria-changelog.7.html">change log</a> for
|
||||
release notes. If you are looking for older <b>Telodendria</b> versions, you
|
||||
can find them <a href="/pub">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
If your operating system has an official package or port of
|
||||
<b>Telodendria</b>, you should prefer to use that instead of manually
|
||||
downloading the source and building it. Consult your operating system's
|
||||
manual for how to install packages, as well as the official repository,
|
||||
to see if a package is available. If your operating system's
|
||||
package or port is too out of date for your tastes, please contact
|
||||
the package's maintainers to notify them, or offer to update the
|
||||
package yourself.
|
||||
</p>
|
||||
<p>
|
||||
If your operating system does <i>not</i> have a package or port of
|
||||
<b>Telodendria</b>, please consult the
|
||||
<a href="/man/man7/porting.7.html">porting(7)</a> page for guidelines
|
||||
related to packaging <b>Telodendria</b> for your system.
|
||||
</p>
|
||||
<h2 id="documentation">Documentation</h2>
|
||||
<p>
|
||||
<b>Telodendria</b>'s documentation is distributed with the source
|
||||
code as <code>man</code> pages, which contain all of the information
|
||||
on what <b>Telodendria</b> is, what its goals are, how to build the source,
|
||||
configure it, as well as contribute to the project. The <code>man</code>
|
||||
pages are also available online for convenience:
|
||||
</p>
|
||||
<p>User Documentation:</p>
|
||||
${USER_DOCS}
|
||||
<br>
|
||||
<details>
|
||||
<summary>Developer Documentation:</summary>
|
||||
<p>
|
||||
This documentation is intended primarily for developers. It details all
|
||||
of the internal workings of Telodendria.
|
||||
</p>
|
||||
${DEV_DOCS}
|
||||
</details>
|
||||
<hr>
|
||||
<p>
|
||||
© 2023 Jordan Bancino <@jordan:bancino.net>
|
||||
<br>
|
||||
Updated on ${DATE}.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
368
site/mandoc.css
368
site/mandoc.css
|
@ -1,368 +0,0 @@
|
|||
/* $OpenBSD: mandoc.css,v 1.39 2022/07/06 14:27:55 schwarze Exp $ */
|
||||
/*
|
||||
* Standard style sheet for mandoc(1) -Thtml and man.cgi(8).
|
||||
*
|
||||
* Written by Ingo Schwarze <schwarze@openbsd.org>.
|
||||
* I place this file into the public domain.
|
||||
* Permission to use, copy, modify, and distribute it for any purpose
|
||||
* with or without fee is hereby granted, without any conditions.
|
||||
*/
|
||||
|
||||
/* Global defaults. */
|
||||
|
||||
html { max-width: 65em;
|
||||
--bg: #FFFFFF;
|
||||
--fg: #000000; }
|
||||
body { background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: Helvetica,Arial,sans-serif; }
|
||||
h1, h2 { font-size: 110%; }
|
||||
table { margin-top: 0em;
|
||||
margin-bottom: 0em;
|
||||
border-collapse: collapse; }
|
||||
/* Some browsers set border-color in a browser style for tbody,
|
||||
* but not for table, resulting in inconsistent border styling. */
|
||||
tbody { border-color: inherit; }
|
||||
tr { border-color: inherit; }
|
||||
td { vertical-align: top;
|
||||
padding-left: 0.2em;
|
||||
padding-right: 0.2em;
|
||||
border-color: inherit; }
|
||||
ul, ol, dl { margin-top: 0em;
|
||||
margin-bottom: 0em; }
|
||||
li, dt { margin-top: 1em; }
|
||||
pre { font-family: inherit; }
|
||||
|
||||
.permalink { border-bottom: thin dotted;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-decoration: inherit; }
|
||||
* { clear: both }
|
||||
|
||||
/* Search form and search results. */
|
||||
|
||||
fieldset { border: thin solid silver;
|
||||
border-radius: 1em;
|
||||
text-align: center; }
|
||||
input[name=expr] {
|
||||
width: 25%; }
|
||||
|
||||
table.results { margin-top: 1em;
|
||||
margin-left: 2em;
|
||||
font-size: smaller; }
|
||||
|
||||
/* Header and footer lines. */
|
||||
|
||||
div[role=doc-pageheader] {
|
||||
display: flex;
|
||||
border-bottom: 1px dotted #808080;
|
||||
margin-bottom: 1em;
|
||||
font-size: smaller; }
|
||||
.head-ltitle { flex: 1; }
|
||||
.head-vol { flex: 0 1 auto;
|
||||
text-align: center; }
|
||||
.head-rtitle { flex: 1;
|
||||
text-align: right; }
|
||||
|
||||
div[role=doc-pagefooter] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-top: 1px dotted #808080;
|
||||
margin-top: 1em;
|
||||
font-size: smaller; }
|
||||
.foot-left { flex: 1; }
|
||||
.foot-date { flex: 0 1 auto;
|
||||
text-align: center; }
|
||||
.foot-os { flex: 1;
|
||||
text-align: right; }
|
||||
|
||||
/* Sections and paragraphs. */
|
||||
|
||||
main { margin-left: 3.8em; }
|
||||
.Nd { }
|
||||
section.Sh { }
|
||||
h2.Sh { margin-top: 1.2em;
|
||||
margin-bottom: 0.6em;
|
||||
margin-left: -3.2em; }
|
||||
section.Ss { }
|
||||
h3.Ss { margin-top: 1.2em;
|
||||
margin-bottom: 0.6em;
|
||||
margin-left: -1.2em;
|
||||
font-size: 105%; }
|
||||
.Pp { margin: 0.6em 0em; }
|
||||
.Sx { }
|
||||
.Xr { }
|
||||
|
||||
/* Displays and lists. */
|
||||
|
||||
.Bd { }
|
||||
.Bd-indent { margin-left: 3.8em; }
|
||||
|
||||
.Bl-bullet { list-style-type: disc;
|
||||
padding-left: 1em; }
|
||||
.Bl-bullet > li { }
|
||||
.Bl-dash { list-style-type: none;
|
||||
padding-left: 0em; }
|
||||
.Bl-dash > li:before {
|
||||
content: "\2014 "; }
|
||||
.Bl-item { list-style-type: none;
|
||||
padding-left: 0em; }
|
||||
.Bl-item > li { }
|
||||
.Bl-compact > li {
|
||||
margin-top: 0em; }
|
||||
|
||||
.Bl-enum { padding-left: 2em; }
|
||||
.Bl-enum > li { }
|
||||
.Bl-compact > li {
|
||||
margin-top: 0em; }
|
||||
|
||||
.Bl-diag { }
|
||||
.Bl-diag > dt {
|
||||
font-style: normal;
|
||||
font-weight: bold; }
|
||||
.Bl-diag > dd {
|
||||
margin-left: 0em; }
|
||||
.Bl-hang { }
|
||||
.Bl-hang > dt { }
|
||||
.Bl-hang > dd {
|
||||
margin-left: 5.5em; }
|
||||
.Bl-inset { }
|
||||
.Bl-inset > dt { }
|
||||
.Bl-inset > dd {
|
||||
margin-left: 0em; }
|
||||
.Bl-ohang { }
|
||||
.Bl-ohang > dt { }
|
||||
.Bl-ohang > dd {
|
||||
margin-left: 0em; }
|
||||
.Bl-tag { margin-top: 0.6em;
|
||||
margin-left: 5.5em; }
|
||||
.Bl-tag > dt {
|
||||
float: left;
|
||||
margin-top: 0em;
|
||||
margin-left: -5.5em;
|
||||
padding-right: 0.5em;
|
||||
vertical-align: top; }
|
||||
.Bl-tag > dd {
|
||||
clear: right;
|
||||
column-count: 1; /* Force block formatting context. */
|
||||
width: 100%;
|
||||
margin-top: 0em;
|
||||
margin-left: 0em;
|
||||
margin-bottom: 0.6em;
|
||||
vertical-align: top; }
|
||||
.Bl-compact { margin-top: 0em; }
|
||||
.Bl-compact > dd {
|
||||
margin-bottom: 0em; }
|
||||
.Bl-compact > dt {
|
||||
margin-top: 0em; }
|
||||
|
||||
.Bl-column { }
|
||||
.Bl-column > tbody > tr { }
|
||||
.Bl-column > tbody > tr > td {
|
||||
margin-top: 1em; }
|
||||
.Bl-compact > tbody > tr > td {
|
||||
margin-top: 0em; }
|
||||
|
||||
.Rs { font-style: normal;
|
||||
font-weight: normal; }
|
||||
.RsA { }
|
||||
.RsB { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.RsC { }
|
||||
.RsD { }
|
||||
.RsI { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.RsJ { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.RsN { }
|
||||
.RsO { }
|
||||
.RsP { }
|
||||
.RsQ { }
|
||||
.RsR { }
|
||||
.RsT { text-decoration: underline; }
|
||||
.RsU { }
|
||||
.RsV { }
|
||||
|
||||
.eqn { }
|
||||
.tbl td { vertical-align: middle; }
|
||||
|
||||
.HP { margin-left: 3.8em;
|
||||
text-indent: -3.8em; }
|
||||
|
||||
/* Semantic markup for command line utilities. */
|
||||
|
||||
table.Nm { }
|
||||
code.Nm { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Fl { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Cm { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Ar { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Op { display: inline flow; }
|
||||
.Ic { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Ev { font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: monospace; }
|
||||
.Pa { font-style: italic;
|
||||
font-weight: normal; }
|
||||
|
||||
/* Semantic markup for function libraries. */
|
||||
|
||||
.Lb { }
|
||||
code.In { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
a.In { }
|
||||
.Fd { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Ft { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Fn { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Fa { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Vt { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Va { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Dv { font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: monospace; }
|
||||
.Er { font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: monospace; }
|
||||
|
||||
/* Various semantic markup. */
|
||||
|
||||
.An { }
|
||||
.Lk { }
|
||||
.Mt { }
|
||||
.Cd { font-style: normal;
|
||||
font-weight: bold;
|
||||
font-family: inherit; }
|
||||
.Ad { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Ms { font-style: normal;
|
||||
font-weight: bold; }
|
||||
.St { }
|
||||
.Ux { }
|
||||
|
||||
/* Physical markup. */
|
||||
|
||||
.Bf { display: inline flow; }
|
||||
.No { font-style: normal;
|
||||
font-weight: normal; }
|
||||
.Em { font-style: italic;
|
||||
font-weight: normal; }
|
||||
.Sy { font-style: normal;
|
||||
font-weight: bold; }
|
||||
.Li { font-style: normal;
|
||||
font-weight: normal;
|
||||
font-family: monospace; }
|
||||
|
||||
/* Tooltip support. */
|
||||
|
||||
h2.Sh, h3.Ss { position: relative; }
|
||||
.An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft,
|
||||
.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs,
|
||||
.St, .Sx, .Sy, .Va, .Vt, .Xr {
|
||||
display: inline flow;
|
||||
position: relative; }
|
||||
|
||||
.An::before { content: "An"; }
|
||||
.Ar::before { content: "Ar"; }
|
||||
.Cd::before { content: "Cd"; }
|
||||
.Cm::before { content: "Cm"; }
|
||||
.Dv::before { content: "Dv"; }
|
||||
.Em::before { content: "Em"; }
|
||||
.Er::before { content: "Er"; }
|
||||
.Ev::before { content: "Ev"; }
|
||||
.Fa::before { content: "Fa"; }
|
||||
.Fd::before { content: "Fd"; }
|
||||
.Fl::before { content: "Fl"; }
|
||||
.Fn::before { content: "Fn"; }
|
||||
.Ft::before { content: "Ft"; }
|
||||
.Ic::before { content: "Ic"; }
|
||||
code.In::before { content: "In"; }
|
||||
.Lb::before { content: "Lb"; }
|
||||
.Lk::before { content: "Lk"; }
|
||||
.Ms::before { content: "Ms"; }
|
||||
.Mt::before { content: "Mt"; }
|
||||
.Nd::before { content: "Nd"; }
|
||||
code.Nm::before { content: "Nm"; }
|
||||
.Pa::before { content: "Pa"; }
|
||||
.Rs::before { content: "Rs"; }
|
||||
h2.Sh::before { content: "Sh"; }
|
||||
h3.Ss::before { content: "Ss"; }
|
||||
.St::before { content: "St"; }
|
||||
.Sx::before { content: "Sx"; }
|
||||
.Sy::before { content: "Sy"; }
|
||||
.Va::before { content: "Va"; }
|
||||
.Vt::before { content: "Vt"; }
|
||||
.Xr::before { content: "Xr"; }
|
||||
|
||||
.An::before, .Ar::before, .Cd::before, .Cm::before,
|
||||
.Dv::before, .Em::before, .Er::before, .Ev::before,
|
||||
.Fa::before, .Fd::before, .Fl::before, .Fn::before, .Ft::before,
|
||||
.Ic::before, code.In::before, .Lb::before, .Lk::before,
|
||||
.Ms::before, .Mt::before, .Nd::before, code.Nm::before,
|
||||
.Pa::before, .Rs::before,
|
||||
h2.Sh::before, h3.Ss::before, .St::before, .Sx::before, .Sy::before,
|
||||
.Va::before, .Vt::before, .Xr::before {
|
||||
opacity: 0;
|
||||
transition: .15s ease opacity;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
box-shadow: 0 0 .35em var(--fg);
|
||||
padding: .15em .25em;
|
||||
white-space: nowrap;
|
||||
font-family: Helvetica,Arial,sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
background: var(--bg);
|
||||
color: var(--fg); }
|
||||
.An:hover::before, .Ar:hover::before, .Cd:hover::before, .Cm:hover::before,
|
||||
.Dv:hover::before, .Em:hover::before, .Er:hover::before, .Ev:hover::before,
|
||||
.Fa:hover::before, .Fd:hover::before, .Fl:hover::before, .Fn:hover::before,
|
||||
.Ft:hover::before, .Ic:hover::before, code.In:hover::before,
|
||||
.Lb:hover::before, .Lk:hover::before, .Ms:hover::before, .Mt:hover::before,
|
||||
.Nd:hover::before, code.Nm:hover::before, .Pa:hover::before,
|
||||
.Rs:hover::before, h2.Sh:hover::before, h3.Ss:hover::before, .St:hover::before,
|
||||
.Sx:hover::before, .Sy:hover::before, .Va:hover::before, .Vt:hover::before,
|
||||
.Xr:hover::before {
|
||||
opacity: 1;
|
||||
pointer-events: inherit; }
|
||||
|
||||
/* Overrides to avoid excessive margins on small devices. */
|
||||
|
||||
@media (max-width: 37.5em) {
|
||||
main { margin-left: 0.5em; }
|
||||
h2.Sh, h3.Ss { margin-left: 0em; }
|
||||
.Bd-indent { margin-left: 2em; }
|
||||
.Bl-hang > dd {
|
||||
margin-left: 2em; }
|
||||
.Bl-tag { margin-left: 2em; }
|
||||
.Bl-tag > dt {
|
||||
margin-left: -2em; }
|
||||
.HP { margin-left: 2em;
|
||||
text-indent: -2em; }
|
||||
}
|
||||
|
||||
/* Overrides for a dark color scheme for accessibility. */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html { --bg: #1E1F21;
|
||||
--fg: #EEEFF1; }
|
||||
:link { color: #BAD7FF; }
|
||||
:visited { color: #F6BAFF; }
|
||||
}
|
114
site/style.css
114
site/style.css
|
@ -1,114 +0,0 @@
|
|||
@import "mandoc.css";
|
||||
|
||||
:root {
|
||||
--border-radius: 10px;
|
||||
|
||||
--color-snippet: #161b22;
|
||||
--color-link: #7b8333;
|
||||
--color-bg: #0d1117;
|
||||
--color-text: #c9d1d9;
|
||||
|
||||
--color-table-border: #30363d;
|
||||
--color-table-accent: #161b22;
|
||||
|
||||
--color-error-bg: #5c6434;
|
||||
--color-error: white;
|
||||
}
|
||||
|
||||
html {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: auto;
|
||||
max-width: 8.5in;
|
||||
padding: 0.25in;
|
||||
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.msg-error {
|
||||
background-color: var(--color-error-bg);
|
||||
color: var(--color-error);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.msg-error a {
|
||||
color: var(--color-error);
|
||||
font-weight: bold;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1, h4, h5, h6 {
|
||||
border-bottom: 1px dashed var(--color-table-border);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px dashed var(--color-table-border);
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-link) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid var(--color-table-border);
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--color-table-accent);
|
||||
}
|
||||
|
||||
/* Thanks Jonah! */
|
||||
#logo {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 50vw;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* Mandoc overrides */
|
||||
|
||||
.Bd {
|
||||
background-color: var(--color-snippet);
|
||||
border-radius: var(--border-radius);
|
||||
padding-left: 10px;
|
||||
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Nm {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.Nm td, .Nm th {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
untrusted comment: signify public key
|
||||
RWTPPnWvnpee8NlygSggQqk5V5oghl6Ikq99bZl5IRQwiRMLaJnq82mw
|
|
@ -28,5 +28,7 @@
|
|||
HashMap *
|
||||
FilterApply(Filter * filter, HashMap * event)
|
||||
{
|
||||
(void) filter;
|
||||
(void) event;
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -128,10 +128,6 @@ start:
|
|||
httpServers = NULL;
|
||||
restart = 0;
|
||||
|
||||
/* For getopt() */
|
||||
opterr = 1;
|
||||
optind = 1;
|
||||
|
||||
/* Local variables */
|
||||
exit = EXIT_SUCCESS;
|
||||
flags = 0;
|
||||
|
|
|
@ -30,8 +30,10 @@
|
|||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <User.h>
|
||||
#include <Cytoplasm/Int64.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
|
||||
#include <User.h>
|
||||
|
||||
int
|
||||
RegTokenValid(RegTokenInfo * token)
|
||||
|
@ -99,8 +101,7 @@ RegTokenDelete(RegTokenInfo * token)
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
Free(token->name);
|
||||
Free(token->owner);
|
||||
RegTokenInfoFree(token);
|
||||
Free(token);
|
||||
return 1;
|
||||
}
|
||||
|
@ -113,6 +114,8 @@ RegTokenGetInfo(Db * db, char *token)
|
|||
DbRef *tokenRef;
|
||||
HashMap *tokenJson;
|
||||
|
||||
char *errp = NULL;
|
||||
|
||||
if (!RegTokenExists(db, token))
|
||||
{
|
||||
return NULL;
|
||||
|
@ -126,47 +129,43 @@ RegTokenGetInfo(Db * db, char *token)
|
|||
tokenJson = DbJson(tokenRef);
|
||||
ret = Malloc(sizeof(RegTokenInfo));
|
||||
|
||||
if (!RegTokenInfoFromJson(tokenJson, ret, &errp))
|
||||
{
|
||||
Log(LOG_ERR, "RegTokenGetInfo(): Database decoding error: %s", errp);
|
||||
RegTokenFree(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret->db = db;
|
||||
ret->ref = tokenRef;
|
||||
|
||||
ret->owner =
|
||||
StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "created_by")));
|
||||
ret->name = StrDuplicate(token);
|
||||
|
||||
ret->expires =
|
||||
JsonValueAsInteger(HashMapGet(tokenJson, "expires_on"));
|
||||
ret->created =
|
||||
JsonValueAsInteger(HashMapGet(tokenJson, "created_on"));
|
||||
|
||||
ret->uses =
|
||||
JsonValueAsInteger(HashMapGet(tokenJson, "uses"));
|
||||
ret->used =
|
||||
JsonValueAsInteger(HashMapGet(tokenJson, "used"));
|
||||
|
||||
ret->grants =
|
||||
UserDecodePrivileges(HashMapGet(tokenJson, "grants"));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
RegTokenFree(RegTokenInfo * tokeninfo)
|
||||
RegTokenFree(RegTokenInfo *tokeninfo)
|
||||
{
|
||||
if (tokeninfo)
|
||||
{
|
||||
Free(tokeninfo->name);
|
||||
Free(tokeninfo->owner);
|
||||
RegTokenInfoFree(tokeninfo);
|
||||
Free(tokeninfo);
|
||||
}
|
||||
}
|
||||
int
|
||||
RegTokenClose(RegTokenInfo * tokeninfo)
|
||||
{
|
||||
HashMap *json;
|
||||
|
||||
if (!tokeninfo)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write object to database. */
|
||||
json = RegTokenInfoToJson(tokeninfo);
|
||||
DbJsonSet(tokeninfo->ref, json); /* Copies json into internal structure. */
|
||||
JsonFree(json);
|
||||
|
||||
return DbUnlock(tokeninfo->db, tokeninfo->ref);
|
||||
}
|
||||
static int
|
||||
|
@ -202,7 +201,6 @@ RegTokenInfo *
|
|||
RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int privileges)
|
||||
{
|
||||
RegTokenInfo *ret;
|
||||
HashMap *tokenJson;
|
||||
|
||||
UInt64 timestamp = UtilServerTs();
|
||||
|
||||
|
@ -235,26 +233,12 @@ RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int
|
|||
return NULL;
|
||||
}
|
||||
ret->name = StrDuplicate(name);
|
||||
ret->owner = StrDuplicate(owner);
|
||||
ret->created_by = StrDuplicate(owner);
|
||||
ret->used = Int64Create(0, 0);
|
||||
ret->uses = uses;
|
||||
ret->created = timestamp;
|
||||
ret->expires = expires;
|
||||
ret->grants = privileges;
|
||||
|
||||
/* Write user info to database. */
|
||||
tokenJson = DbJson(ret->ref);
|
||||
HashMapSet(tokenJson, "created_by",
|
||||
JsonValueString(ret->owner));
|
||||
HashMapSet(tokenJson, "created_on",
|
||||
JsonValueInteger(ret->created));
|
||||
HashMapSet(tokenJson, "expires_on",
|
||||
JsonValueInteger(ret->expires));
|
||||
HashMapSet(tokenJson, "used",
|
||||
JsonValueInteger(ret->used));
|
||||
HashMapSet(tokenJson, "uses",
|
||||
JsonValueInteger(ret->uses));
|
||||
HashMapSet(tokenJson, "grants", UserEncodePrivileges(privileges));
|
||||
ret->created_on = timestamp;
|
||||
ret->expires_on = expires;
|
||||
ret->grants = UserEncodePrivileges(privileges);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ struct Room
|
|||
Room *
|
||||
RoomCreate(Db * db, RoomCreateRequest * req)
|
||||
{
|
||||
(void) db;
|
||||
(void) req;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,8 @@ RouterBuild(void)
|
|||
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
||||
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
||||
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
||||
R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens);
|
||||
R("/_telodendria/admin/v1/tokens", RouteAdminTokens);
|
||||
|
||||
#undef R
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <User.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
|
||||
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
||||
{
|
||||
|
|
207
src/Routes/RouteAdminTokens.c
Normal file
207
src/Routes/RouteAdminTokens.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Routes.h>
|
||||
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
|
||||
#include <RegToken.h>
|
||||
#include <User.h>
|
||||
|
||||
ROUTE_IMPL(RouteAdminTokens, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
|
||||
char *token;
|
||||
char *msg;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
|
||||
HttpRequestMethod method = HttpRequestMethodGet(args->context);
|
||||
|
||||
Array *tokensarray;
|
||||
Array *tokens;
|
||||
|
||||
RegTokenInfo *info;
|
||||
|
||||
RegTokenInfo *req;
|
||||
|
||||
size_t i;
|
||||
|
||||
if (method != HTTP_GET && method != HTTP_POST && method != HTTP_DELETE)
|
||||
{
|
||||
msg = "Route only supports GET, POST, and DELETE";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
user = UserAuthenticate(db, token);
|
||||
if (!user)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!(UserGetPrivileges(user) & USER_ISSUE_TOKENS))
|
||||
{
|
||||
msg = "User doesn't have the ISSUE_TOKENS privilege.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case HTTP_GET:
|
||||
if (ArraySize(path) == 0)
|
||||
{
|
||||
tokensarray = ArrayCreate();
|
||||
|
||||
/* Get all registration tokens */
|
||||
tokens = DbList(db, 2, "tokens", "registration");
|
||||
|
||||
response = HashMapCreate();
|
||||
|
||||
for (i = 0; i < ArraySize(tokens); i++)
|
||||
{
|
||||
char *tokenname = ArrayGet(tokens, i);
|
||||
HashMap *jsoninfo;
|
||||
|
||||
info = RegTokenGetInfo(db, tokenname);
|
||||
jsoninfo = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
|
||||
ArrayAdd(tokensarray, JsonValueObject(jsoninfo));
|
||||
}
|
||||
|
||||
JsonSet(response, JsonValueArray(tokensarray), 1, "tokens");
|
||||
|
||||
DbListFree(tokens);
|
||||
break;
|
||||
}
|
||||
|
||||
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||
if (!info)
|
||||
{
|
||||
msg = "Token doesn't exist.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
response = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
break;
|
||||
case HTTP_POST:
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
req = Malloc(sizeof(RegTokenInfo));
|
||||
memset(req, 0, sizeof(RegTokenInfo));
|
||||
|
||||
if (!RegTokenInfoFromJson(request, req, &msg))
|
||||
{
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
|
||||
}
|
||||
|
||||
if (!req->name)
|
||||
{
|
||||
req->name = StrRandom(16);
|
||||
}
|
||||
|
||||
/* Create the actual token that will be stored. */
|
||||
info = RegTokenCreate(db, req->name, UserGetName(user),
|
||||
req->expires_on, req->uses,
|
||||
UserDecodePrivileges(req->grants));
|
||||
if (!info)
|
||||
{
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
msg = "Cannot create token.";
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
response = RegTokenInfoToJson(info);
|
||||
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
RegTokenInfoFree(req);
|
||||
Free(req);
|
||||
break;
|
||||
case HTTP_DELETE:
|
||||
if (ArraySize(path) == 0)
|
||||
{
|
||||
msg = "No registration token given to DELETE /tokens/[token].";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
info = RegTokenGetInfo(db, ArrayGet(path, 0));
|
||||
RegTokenDelete(info);
|
||||
|
||||
response = HashMapCreate();
|
||||
break;
|
||||
default:
|
||||
/* Should not be possible. */
|
||||
break;
|
||||
}
|
||||
finish:
|
||||
UserUnlock(user);
|
||||
JsonFree(request);
|
||||
return response;
|
||||
}
|
|
@ -65,6 +65,8 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
|||
char *token;
|
||||
char *newPassword;
|
||||
|
||||
char *msg;
|
||||
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
if (!config)
|
||||
|
@ -78,8 +80,9 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
|||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "Route only supports POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -118,9 +121,10 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
|||
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
||||
if (!newPassword)
|
||||
{
|
||||
msg = "'new_password' is unset or not a string.";
|
||||
JsonFree(request);
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,12 +33,14 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
|||
RouteArgs *args = argp;
|
||||
HashMap *response;
|
||||
char *token;
|
||||
char *msg;
|
||||
|
||||
User *user = NULL;
|
||||
Config *config = NULL;
|
||||
|
||||
HashMap *request = NULL;
|
||||
Config *newConf;
|
||||
HashMap *newJson = NULL;
|
||||
|
||||
(void) path;
|
||||
|
||||
|
@ -58,17 +60,19 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
|||
|
||||
if (!(UserGetPrivileges(user) & USER_CONFIG))
|
||||
{
|
||||
msg = "User does not have the 'CONFIG' privilege.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
config = ConfigLock(args->matrixArgs->db);
|
||||
if (!config)
|
||||
{
|
||||
msg = "Internal server error while locking configuration.";
|
||||
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -89,8 +93,9 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
|||
newConf = ConfigParse(request);
|
||||
if (!newConf)
|
||||
{
|
||||
msg = "Internal server error while parsing config.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -107,8 +112,9 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
|||
}
|
||||
else
|
||||
{
|
||||
msg = "Internal server error while writing the config.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -121,10 +127,59 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
|||
JsonFree(request);
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
/* TODO: Support incremental changes to the config */
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
if (!request)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
newJson = JsonDuplicate(DbJson(config->ref));
|
||||
JsonMerge(newJson, request);
|
||||
|
||||
newConf = ConfigParse(newJson);
|
||||
|
||||
if (!newConf)
|
||||
{
|
||||
msg = "Internal server error while parsing config.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (newConf->ok)
|
||||
{
|
||||
if (DbJsonSet(config->ref, newJson))
|
||||
{
|
||||
response = HashMapCreate();
|
||||
/*
|
||||
* TODO: Apply configuration and set this only if a main
|
||||
* component was reconfigured, such as the listeners.
|
||||
*/
|
||||
HashMapSet(response, "restart_required", JsonValueBoolean(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "Internal server error while writing the config.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, newConf->err);
|
||||
}
|
||||
|
||||
ConfigFree(newConf);
|
||||
JsonFree(request);
|
||||
JsonFree(newJson);
|
||||
break;
|
||||
default:
|
||||
msg = "Unknown request method.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,9 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
|
|||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
err = "Unknown request method.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
|||
User *user = NULL;
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
char *msg;
|
||||
|
||||
(void) path;
|
||||
|
||||
if (!config)
|
||||
|
@ -59,8 +61,9 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
|||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "Route only accepts POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -128,8 +131,9 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
|||
|
||||
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
|
||||
{
|
||||
msg = "Internal server error: couldn't remove user properly.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,8 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
|
||||
char *userParam = ArrayGet(path, 0);
|
||||
|
||||
char *msg;
|
||||
|
||||
if (!userParam)
|
||||
{
|
||||
/* Should be impossible */
|
||||
|
@ -87,15 +89,17 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
id = UserIdParse(userParam, serverName);
|
||||
if (!id)
|
||||
{
|
||||
msg = "Invalid user ID.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!StrEquals(id->server, serverName))
|
||||
{
|
||||
msg = "Cannot use /filter for non-local users.";
|
||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNAUTHORIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -115,8 +119,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
|
||||
if (!StrEquals(id->localpart, UserGetName(user)))
|
||||
{
|
||||
msg = "Unauthorized to use /filter.";
|
||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -126,8 +131,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
|
||||
if (!ref)
|
||||
{
|
||||
msg = "The filter for this user was not found.";
|
||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -161,8 +167,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
filterId = StrRandom(12);
|
||||
if (!filterId)
|
||||
{
|
||||
msg = "Couldn't generate random filter ID; this is unintended.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -170,8 +177,9 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
|||
if (!ref)
|
||||
{
|
||||
Free(filterId);
|
||||
msg = "Couldn't write filter to the database, this is unintended.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include <Schema/LoginRequest.h>
|
||||
|
||||
#include <Cytoplasm/Json.h>
|
||||
#include <Cytoplasm/HashMap.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
@ -43,12 +45,9 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
|
||||
HashMap *identifier;
|
||||
|
||||
char *deviceId = NULL;
|
||||
char *initialDeviceDisplayName = NULL;
|
||||
int refreshToken = 0;
|
||||
LoginRequest loginRequest;
|
||||
LoginRequestUserIdentifier userIdentifier;
|
||||
|
||||
char *password;
|
||||
char *type;
|
||||
UserId *userId = NULL;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
@ -57,6 +56,14 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
UserLoginInfo *loginInfo;
|
||||
char *fullUsername;
|
||||
|
||||
char *type;
|
||||
char *initialDeviceDisplayName;
|
||||
char *deviceId;
|
||||
char *password;
|
||||
int refreshToken;
|
||||
|
||||
char *msg;
|
||||
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
if (!config)
|
||||
|
@ -68,6 +75,9 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
|
||||
(void) path;
|
||||
|
||||
memset(&loginRequest, 0, sizeof(LoginRequest));
|
||||
memset(&userIdentifier, 0, sizeof(LoginRequestUserIdentifier));
|
||||
|
||||
switch (HttpRequestMethodGet(args->context))
|
||||
{
|
||||
case HTTP_GET:
|
||||
|
@ -88,49 +98,27 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
break;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "type");
|
||||
if (!val)
|
||||
if (!LoginRequestFromJson(request, &loginRequest, &msg))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
if (loginRequest.type != REQUEST_TYPE_PASSWORD)
|
||||
{
|
||||
msg = "Unsupported login type.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
type = JsonValueAsString(val);
|
||||
if (!StrEquals(type, "m.login.password"))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "identifier");
|
||||
if (!val)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) != JSON_OBJECT)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
identifier = JsonValueAsObject(val);
|
||||
identifier = loginRequest.identifier;
|
||||
|
||||
val = HashMapGet(identifier, "type");
|
||||
if (!val)
|
||||
{
|
||||
msg = "No login identifier type set.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
break;
|
||||
|
@ -138,112 +126,60 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
msg = "Invalid login identifier type.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
type = JsonValueAsString(val);
|
||||
if (!StrEquals(type, "m.id.user"))
|
||||
{
|
||||
msg = "Invalid login identifier type.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
val = HashMapGet(identifier, "user");
|
||||
if (!val)
|
||||
if (!LoginRequestUserIdentifierFromJson(identifier,
|
||||
&userIdentifier, &msg))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
userId = UserIdParse(JsonValueAsString(val), config->serverName);
|
||||
userId = UserIdParse(userIdentifier.user, config->serverName);
|
||||
if (!userId)
|
||||
{
|
||||
msg = "Invalid user ID.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!StrEquals(userId->server, config->serverName)
|
||||
|| !UserExists(db, userId->localpart))
|
||||
{
|
||||
msg = "Unknown user ID.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
deviceId = loginRequest.device_id;
|
||||
|
||||
val = HashMapGet(request, "device_id");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
deviceId = JsonValueAsString(val);
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "initial_device_display_name");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
initialDeviceDisplayName = JsonValueAsString(val);
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "password");
|
||||
if (!val)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
password = JsonValueAsString(val);
|
||||
|
||||
val = HashMapGet(request, "refresh_token");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_BOOLEAN)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
refreshToken = JsonValueAsBoolean(val);
|
||||
}
|
||||
initialDeviceDisplayName =loginRequest.initial_device_display_name;
|
||||
password = loginRequest.password;
|
||||
refreshToken = loginRequest.refresh_token;
|
||||
|
||||
user = UserLock(db, userId->localpart);
|
||||
|
||||
if (!user)
|
||||
{
|
||||
msg = "Couldn't lock user.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -261,10 +197,11 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
|
||||
if (!loginInfo)
|
||||
{
|
||||
msg = "Invalid creditentials for user.";
|
||||
UserUnlock(user);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -300,8 +237,9 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
|
||||
break;
|
||||
default:
|
||||
msg = "Route only accepts GET and POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -309,5 +247,8 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
|||
JsonFree(request);
|
||||
ConfigUnlock(config);
|
||||
|
||||
LoginRequestFree(&loginRequest);
|
||||
LoginRequestUserIdentifierFree(&userIdentifier);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -38,14 +38,17 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
|||
|
||||
char *tokenstr;
|
||||
|
||||
char *msg;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user;
|
||||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "This route only accepts POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &tokenstr);
|
||||
|
@ -84,8 +87,9 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
|||
{
|
||||
if (!UserDeleteToken(user, tokenstr))
|
||||
{
|
||||
msg = "Internal server error: couldn't delete token.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
|||
JsonValue *val;
|
||||
int privileges;
|
||||
|
||||
char *msg;
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
if (response)
|
||||
{
|
||||
|
@ -55,8 +57,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
|||
|
||||
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
|
||||
{
|
||||
msg = "User doesn't have the GRANT_PRIVILEGES privilege";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -68,8 +71,9 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
|||
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
|
||||
if (!user)
|
||||
{
|
||||
msg = "Unknown user.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
@ -90,23 +94,24 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
|||
val = HashMapGet(request, "privileges");
|
||||
if (!val || JsonValueType(val) != JSON_ARRAY)
|
||||
{
|
||||
msg = "'privileges' is unset or not an array.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (HttpRequestMethodGet(args->context))
|
||||
{
|
||||
case HTTP_POST:
|
||||
privileges = UserDecodePrivileges(val);
|
||||
privileges = UserDecodePrivileges(JsonValueAsArray(val));
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
privileges = UserGetPrivileges(user);
|
||||
privileges |= UserDecodePrivileges(val);
|
||||
privileges |= UserDecodePrivileges(JsonValueAsArray(val));
|
||||
break;
|
||||
case HTTP_DELETE:
|
||||
privileges = UserGetPrivileges(user);
|
||||
privileges &= ~UserDecodePrivileges(val);
|
||||
privileges &= ~UserDecodePrivileges(JsonValueAsArray(val));
|
||||
break;
|
||||
default:
|
||||
/* Impossible */
|
||||
|
@ -116,19 +121,21 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
|||
|
||||
if (!UserSetPrivileges(user, privileges))
|
||||
{
|
||||
msg = "Internal server error: couldn't set privileges.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Fall through */
|
||||
case HTTP_GET:
|
||||
response = HashMapCreate();
|
||||
HashMapSet(response, "privileges", UserEncodePrivileges(UserGetPrivileges(user)));
|
||||
HashMapSet(response, "privileges", JsonValueArray(UserEncodePrivileges(UserGetPrivileges(user))));
|
||||
break;
|
||||
default:
|
||||
msg = "Route only accepts POST, PUT, DELETE, and GET.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
|||
char *op = ArrayGet(path, 0);
|
||||
HashMap *response;
|
||||
char *token;
|
||||
char *msg;
|
||||
User *user = NULL;
|
||||
|
||||
response = MatrixGetAccessToken(args->context, &token);
|
||||
|
@ -55,11 +56,13 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
|||
|
||||
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
|
||||
{
|
||||
msg = "User doesn't have PROC_CONTROL privilege.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
msg = "Unknown operation.";
|
||||
switch (HttpRequestMethodGet(args->context))
|
||||
{
|
||||
case HTTP_POST:
|
||||
|
@ -74,7 +77,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
|||
else
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
break;
|
||||
|
@ -106,12 +109,12 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
|||
else
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
default:
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
|||
UserAccessToken *newAccessToken;
|
||||
char *deviceId;
|
||||
|
||||
char *msg;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
|
@ -55,8 +57,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
|||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "This route only accepts POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
|
@ -69,8 +72,9 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
|||
val = HashMapGet(request, "refresh_token");
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
msg = "'refresh_token' is unset or not a string.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
|
||||
#include <Schema/Registration.h>
|
||||
|
||||
#include <User.h>
|
||||
#include <Uia.h>
|
||||
#include <RegToken.h>
|
||||
|
@ -55,22 +57,15 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
|
||||
JsonValue *val;
|
||||
RegistrationRequest regReq;
|
||||
|
||||
char *kind;
|
||||
|
||||
char *username = NULL;
|
||||
char *password = NULL;
|
||||
char *initialDeviceDisplayName = NULL;
|
||||
int refreshToken = 0;
|
||||
int inhibitLogin = 0;
|
||||
char *deviceId = NULL;
|
||||
char *fullUsername;
|
||||
char *msg;
|
||||
char *username;
|
||||
|
||||
Db *db = args->matrixArgs->db;
|
||||
|
||||
User *user = NULL;
|
||||
|
||||
Array *uiaFlows = NULL;
|
||||
int uiaResult;
|
||||
|
||||
|
@ -79,11 +74,22 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
regReq.username = NULL;
|
||||
regReq.password = NULL;
|
||||
regReq.device_id = NULL;
|
||||
regReq.initial_device_display_name = NULL;
|
||||
regReq.refresh_token = 0;
|
||||
regReq.inhibit_login = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
if (!config)
|
||||
{
|
||||
msg = "Internal server error while locking configuration.";
|
||||
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
|
||||
if (ArraySize(path) == 0)
|
||||
|
@ -102,26 +108,23 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "username");
|
||||
if (val)
|
||||
if (!RegistrationRequestFromJson(request, ®Req, &msg))
|
||||
{
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
username = StrDuplicate(JsonValueAsString(val));
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_NOT_JSON, msg);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!UserValidate(username, config->serverName))
|
||||
if (regReq.username)
|
||||
{
|
||||
if (!UserValidate(regReq.username, config->serverName))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (UserExists(db, username))
|
||||
if (UserExists(db, regReq.username))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
|
||||
|
@ -158,99 +161,44 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
/* We don't support guest accounts yet */
|
||||
if (kind && !StrEquals(kind, "user"))
|
||||
{
|
||||
msg = "Guest accounts are currently not supported";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "password");
|
||||
if (!val)
|
||||
if (!regReq.password)
|
||||
{
|
||||
msg = "'password' field is unset";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
/* All of the other fields are optional, we don't have to check
|
||||
* them. */
|
||||
|
||||
password = StrDuplicate(JsonValueAsString(val));
|
||||
|
||||
val = HashMapGet(request, "device_id");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
deviceId = StrDuplicate(JsonValueAsString(val));
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "inhibit_login");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_BOOLEAN)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
inhibitLogin = JsonValueAsBoolean(val);
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "initial_device_display_name");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
initialDeviceDisplayName = StrDuplicate(JsonValueAsString(val));
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "refresh_token");
|
||||
if (val)
|
||||
{
|
||||
if (JsonValueType(val) != JSON_BOOLEAN)
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
refreshToken = JsonValueAsBoolean(val);
|
||||
}
|
||||
|
||||
user = UserCreate(db, username, password);
|
||||
user = UserCreate(db, regReq.username, regReq.password);
|
||||
response = HashMapCreate();
|
||||
|
||||
fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
|
||||
fullUsername = StrConcat(4,
|
||||
"@", UserGetName(user), ":", config->serverName);
|
||||
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
||||
Free(fullUsername);
|
||||
|
||||
HttpResponseStatus(args->context, HTTP_OK);
|
||||
if (!inhibitLogin)
|
||||
if (!regReq.inhibit_login)
|
||||
{
|
||||
UserLoginInfo *loginInfo = UserLogin(user, password, deviceId,
|
||||
initialDeviceDisplayName, refreshToken);
|
||||
UserLoginInfo *loginInfo = UserLogin(user, regReq.password,
|
||||
regReq.device_id, regReq.initial_device_display_name,
|
||||
regReq.refresh_token);
|
||||
|
||||
HashMapSet(response, "access_token",
|
||||
JsonValueString(loginInfo->accessToken->string));
|
||||
HashMapSet(response, "device_id",
|
||||
JsonValueString(loginInfo->accessToken->deviceId));
|
||||
|
||||
if (refreshToken)
|
||||
if (regReq.refresh_token)
|
||||
{
|
||||
HashMapSet(response, "expires_in_ms",
|
||||
JsonValueInteger(loginInfo->accessToken->lifetime));
|
||||
|
@ -276,7 +224,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
|
||||
if (info)
|
||||
{
|
||||
UserSetPrivileges(user, info->grants);
|
||||
UserSetPrivileges(user, UserDecodePrivileges(info->grants));
|
||||
RegTokenClose(info);
|
||||
RegTokenFree(info);
|
||||
}
|
||||
|
@ -294,10 +242,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
|||
UserUnlock(user);
|
||||
finish:
|
||||
UiaFlowsFree(uiaFlows);
|
||||
Free(username);
|
||||
Free(password);
|
||||
Free(deviceId);
|
||||
Free(initialDeviceDisplayName);
|
||||
RegistrationRequestFree(®Req);
|
||||
JsonFree(request);
|
||||
}
|
||||
else
|
||||
|
@ -310,8 +255,9 @@ finish:
|
|||
|
||||
if (!username)
|
||||
{
|
||||
msg = "'username' path parameter is not set.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
|
||||
}
|
||||
else if (!UserValidate(username, config->serverName))
|
||||
{
|
||||
|
|
|
@ -26,19 +26,37 @@
|
|||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Json.h>
|
||||
|
||||
#include <Schema/RequestToken.h>
|
||||
|
||||
ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||
{
|
||||
RouteArgs *args = argp;
|
||||
char *type = ArrayGet(path, 0);
|
||||
HashMap *request;
|
||||
HashMap *response;
|
||||
JsonValue *val;
|
||||
char *str;
|
||||
|
||||
char *msg;
|
||||
|
||||
RequestToken reqTok;
|
||||
|
||||
Int64 minusOne = Int64Neg(Int64Create(0, 1));
|
||||
|
||||
reqTok.client_secret = NULL;
|
||||
reqTok.next_link = NULL;
|
||||
reqTok.id_access_token = NULL;
|
||||
reqTok.id_server = NULL;
|
||||
|
||||
reqTok.email = NULL;
|
||||
reqTok.country = NULL;
|
||||
reqTok.phone_number = NULL;
|
||||
|
||||
reqTok.send_attempt = minusOne;
|
||||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||
{
|
||||
msg = "This route only accepts POST.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
|
@ -48,87 +66,92 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
|||
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "client_secret");
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
if (!RequestTokenFromJson(request, &reqTok, &msg))
|
||||
{
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
str = JsonValueAsString(val);
|
||||
if (strlen(str) > 255 || StrBlank(str))
|
||||
if (!reqTok.client_secret)
|
||||
{
|
||||
msg = "'client_secret' is not set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "send_attempt");
|
||||
if (!val || JsonValueType(val) != JSON_INTEGER)
|
||||
if (strlen(reqTok.client_secret) > 255 || StrBlank(reqTok.client_secret))
|
||||
{
|
||||
msg = "'client_secret' is blank or too long";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "next_link");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (Int64Eq(reqTok.send_attempt, minusOne))
|
||||
{
|
||||
msg = "Invalid or inexistent 'send_attempt'";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "id_access_token");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (!reqTok.next_link)
|
||||
{
|
||||
msg = "'next_link' is not set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "id_server");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (!reqTok.id_access_token)
|
||||
{
|
||||
msg = "'id_access_token' is not set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
if (!reqTok.id_server)
|
||||
{
|
||||
msg = "'id_server' is not set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (StrEquals(type, "email"))
|
||||
{
|
||||
val = HashMapGet(request, "email");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (!reqTok.email)
|
||||
{
|
||||
msg = "Type is set to 'email' yet none was set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
else if (StrEquals(type, "msisdn"))
|
||||
{
|
||||
val = HashMapGet(request, "country");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (!reqTok.country)
|
||||
{
|
||||
msg = "Type is set to 'msisdn' yet no country is set";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
str = JsonValueAsString(val);
|
||||
if (strlen(str) != 2)
|
||||
if (strlen(reqTok.country) != 2)
|
||||
{
|
||||
msg = "Invalid country tag, length must be 2";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
val = HashMapGet(request, "phone_number");
|
||||
if (val && JsonValueType(val) != JSON_STRING)
|
||||
if (!reqTok.phone_number)
|
||||
{
|
||||
msg = "Type is set to 'msisdn' yet phone_number is unset";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
@ -145,5 +168,6 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
|||
|
||||
finish:
|
||||
JsonFree(request);
|
||||
RequestTokenFree(&reqTok);
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,10 @@ ROUTE_IMPL(RouteRoomAliases, path, argp)
|
|||
Db *db = args->matrixArgs->db;
|
||||
DbRef *ref = NULL;
|
||||
|
||||
(void) roomId;
|
||||
|
||||
/* TODO: Placeholder; remove. */
|
||||
goto finish;
|
||||
finish:
|
||||
DbUnlock(db, ref);
|
||||
JsonFree(request);
|
||||
|
|
|
@ -47,8 +47,8 @@ ROUTE_IMPL(RouteStaticResources, path, argp)
|
|||
"function findGetParameter(parameterName) {"
|
||||
" var result = null;"
|
||||
" var tmp = [];"
|
||||
" var items = location.search.substr(1).split(\"&\");"
|
||||
" for (var index = 0; index < items.length; index++) {"
|
||||
" var items = location.search.substr(1).split(\"&\");"
|
||||
" for (var index = 0; index < items.length; index++) {"
|
||||
" tmp = items[index].split(\"=\");"
|
||||
" if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);"
|
||||
" }"
|
||||
|
|
|
@ -41,13 +41,15 @@ ROUTE_IMPL(RouteTokenValid, path, argp)
|
|||
RegTokenInfo *info = NULL;
|
||||
|
||||
char *tokenstr;
|
||||
char *msg;
|
||||
|
||||
(void) path;
|
||||
|
||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||
{
|
||||
msg = "This route only accepts GET.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
|
|
|
@ -36,6 +36,8 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
|||
char *authType = ArrayGet(path, 0);
|
||||
char *sessionId;
|
||||
|
||||
char *msg;
|
||||
|
||||
if (!authType)
|
||||
{
|
||||
/* This should never happen */
|
||||
|
@ -56,9 +58,10 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
|||
config = ConfigLock(args->matrixArgs->db);
|
||||
if (!config)
|
||||
{
|
||||
msg = "Internal server error: failed to lock configuration.";
|
||||
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
|
||||
request = JsonDecode(HttpServerStream(args->context));
|
||||
|
@ -93,15 +96,17 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
|||
}
|
||||
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||
{
|
||||
msg = "Route only supports GET.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
}
|
||||
|
||||
sessionId = HashMapGet(requestParams, "session");
|
||||
if (!sessionId)
|
||||
{
|
||||
msg = "'session' parameter is unset.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
return MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||
return MatrixErrorCreate(M_MISSING_PARAM, msg);
|
||||
}
|
||||
|
||||
HttpResponseHeader(args->context, "Content-Type", "text/html");
|
||||
|
@ -121,25 +126,25 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
|||
HtmlEndForm(stream);
|
||||
HtmlBeginJs(stream);
|
||||
StreamPrintf(stream,
|
||||
"function buildRequest() {"
|
||||
"function buildRequest() {"
|
||||
" let user = document.getElementById('user').value;"
|
||||
" let pass = document.getElementById('password').value;"
|
||||
" if (!user || !pass) {"
|
||||
" setFormError('Please specify a username and password.');"
|
||||
" return false;"
|
||||
" }"
|
||||
" return {"
|
||||
" auth: {"
|
||||
" type: '%s',"
|
||||
" identifier: {"
|
||||
" type: 'm.id.user',"
|
||||
" user: user"
|
||||
" },"
|
||||
" password: pass,"
|
||||
" session: '%s'"
|
||||
" }"
|
||||
" };"
|
||||
"}", authType, sessionId);
|
||||
" let pass = document.getElementById('password').value;"
|
||||
" if (!user || !pass) {"
|
||||
" setFormError('Please specify a username and password.');"
|
||||
" return false;"
|
||||
" }"
|
||||
" return {"
|
||||
" auth: {"
|
||||
" type: '%s',"
|
||||
" identifier: {"
|
||||
" type: 'm.id.user',"
|
||||
" user: user"
|
||||
" },"
|
||||
" password: pass,"
|
||||
" session: '%s'"
|
||||
" }"
|
||||
" };"
|
||||
"}", authType, sessionId);
|
||||
HtmlEndJs(stream);
|
||||
}
|
||||
else if (StrEquals(authType, "m.login.registration_token"))
|
||||
|
@ -186,10 +191,10 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
|||
"function processResponse(xhr) {"
|
||||
" let r = JSON.parse(xhr.responseText);"
|
||||
" console.log(r);"
|
||||
" if (xhr.status == 200 || r.completed.includes('%s')) {"
|
||||
" if (xhr.status == 200 || r.completed.includes('%s')) {"
|
||||
" if (window.onAuthDone) {"
|
||||
" window.onAuthDone();"
|
||||
" } else if (window.opener && window.opener.postMessage) {"
|
||||
" } else if (window.opener && window.opener.postMessage) {"
|
||||
" window.opener.postMessage('authDone', '*');"
|
||||
" } else {"
|
||||
" setFormError('Client error.');"
|
||||
|
|
|
@ -48,6 +48,8 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
char *token = NULL;
|
||||
char *value = NULL;
|
||||
|
||||
char *msg;
|
||||
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
if (!config)
|
||||
|
@ -63,15 +65,18 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
userId = UserIdParse(username, serverName);
|
||||
if (!userId)
|
||||
{
|
||||
msg = "Invalid user ID.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
||||
goto finish;
|
||||
}
|
||||
if (strcmp(userId->server, serverName))
|
||||
{
|
||||
/* TODO: Implement lookup over federation. */
|
||||
msg = "User profile endpoint currently doesn't support lookup over "
|
||||
"federation.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -82,8 +87,9 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
user = UserLock(db, userId->localpart);
|
||||
if (!user)
|
||||
{
|
||||
msg = "Couldn't lock user.";
|
||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -138,7 +144,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
StrEquals(entry, "avatar_url"))
|
||||
{
|
||||
/* Check if user has privilege to do that action. */
|
||||
if (strcmp(userId->localpart, UserGetName(user)) == 0)
|
||||
if (StrEquals(userId->localpart, UserGetName(user)))
|
||||
{
|
||||
value = JsonValueAsString(HashMapGet(request, entry));
|
||||
/* TODO: Make UserSetProfile notify other
|
||||
|
@ -148,14 +154,16 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
goto finish;
|
||||
}
|
||||
/* User is not allowed to carry-on the action */
|
||||
msg = "Cannot change another user's profile.";
|
||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
||||
goto finish;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "Invalid property being changed.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
@ -166,8 +174,9 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
|||
goto finish;
|
||||
}
|
||||
default:
|
||||
msg = "Route only accepts GET and PUT.";
|
||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
break;
|
||||
}
|
||||
finish:
|
||||
|
|
|
@ -35,7 +35,12 @@ ROUTE_IMPL(RouteVersions, path, argp)
|
|||
(void) path;
|
||||
(void) argp;
|
||||
|
||||
ArrayAdd(versions, JsonValueString("v1.6"));
|
||||
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
|
||||
|
||||
DECLARE_SPEC_VERSION("v1.7");
|
||||
/* Declare additional spec version support here. */
|
||||
|
||||
#undef DECLARE_SPEC_VERSION
|
||||
|
||||
HashMapSet(response, "versions", JsonValueArray(versions));
|
||||
return response;
|
||||
|
|
|
@ -37,11 +37,14 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
|
|||
|
||||
Config *config = ConfigLock(args->matrixArgs->db);
|
||||
|
||||
char *msg;
|
||||
|
||||
if (!config)
|
||||
{
|
||||
Log(LOG_ERR, "Well-known endpoint failed to lock configuration.");
|
||||
msg = "Internal server error: couldn't lock database.";
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
|
||||
if (StrEquals(ArrayGet(path, 0), "client"))
|
||||
|
|
|
@ -42,14 +42,16 @@ ROUTE_IMPL(RouteWhoami, path, argp)
|
|||
char *token;
|
||||
char *userID;
|
||||
char *deviceID;
|
||||
char *msg;
|
||||
|
||||
Config *config = ConfigLock(db);
|
||||
|
||||
if (!config)
|
||||
{
|
||||
msg = "Internal server error: couldn't lock database.";
|
||||
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
|
||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
||||
}
|
||||
|
||||
(void) path;
|
||||
|
|
|
@ -33,12 +33,14 @@
|
|||
static HashMap *
|
||||
StateResolveV1(Array * states)
|
||||
{
|
||||
(void) states;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HashMap *
|
||||
StateResolveV2(Array * states)
|
||||
{
|
||||
(void) states;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
11
src/Uia.c
11
src/Uia.c
|
@ -222,6 +222,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
|||
HashMap *dbJson;
|
||||
int ret;
|
||||
|
||||
char *msg;
|
||||
|
||||
if (!flows)
|
||||
{
|
||||
return -1;
|
||||
|
@ -242,8 +244,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
|||
|
||||
if (JsonValueType(val) != JSON_OBJECT)
|
||||
{
|
||||
msg = "'auth' is not an object.";
|
||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -252,8 +255,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
|||
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
msg = "'auth->session' is unset or not a string.";
|
||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -311,8 +315,9 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
|||
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
msg = "'auth->type' is unset or not a string.";
|
||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
||||
ret = 0;
|
||||
goto finish;
|
||||
}
|
||||
|
|
29
src/User.c
29
src/User.c
|
@ -749,7 +749,7 @@ UserGetPrivileges(User * user)
|
|||
return USER_NONE;
|
||||
}
|
||||
|
||||
return UserDecodePrivileges(HashMapGet(DbJson(user->ref), "privileges"));
|
||||
return UserDecodePrivileges(JsonValueAsArray(HashMapGet(DbJson(user->ref), "privileges")));
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -768,7 +768,7 @@ UserSetPrivileges(User * user, int privileges)
|
|||
return 1;
|
||||
}
|
||||
|
||||
val = UserEncodePrivileges(privileges);
|
||||
val = JsonValueArray(UserEncodePrivileges(privileges));
|
||||
if (!val)
|
||||
{
|
||||
return 0;
|
||||
|
@ -779,31 +779,26 @@ UserSetPrivileges(User * user, int privileges)
|
|||
}
|
||||
|
||||
int
|
||||
UserDecodePrivileges(JsonValue * val)
|
||||
UserDecodePrivileges(Array * arr)
|
||||
{
|
||||
int privileges = USER_NONE;
|
||||
|
||||
size_t i;
|
||||
Array *arr;
|
||||
|
||||
if (!val)
|
||||
if (!arr)
|
||||
{
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (JsonValueType(val) == JSON_ARRAY)
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
{
|
||||
arr = JsonValueAsArray(val);
|
||||
for (i = 0; i < ArraySize(arr); i++)
|
||||
JsonValue *val = ArrayGet(arr, i);
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
val = ArrayGet(arr, i);
|
||||
if (!val || JsonValueType(val) != JSON_STRING)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
||||
continue;
|
||||
}
|
||||
|
||||
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
||||
}
|
||||
|
||||
finish:
|
||||
|
@ -851,7 +846,7 @@ UserDecodePrivilege(const char *p)
|
|||
}
|
||||
}
|
||||
|
||||
JsonValue *
|
||||
Array *
|
||||
UserEncodePrivileges(int privileges)
|
||||
{
|
||||
Array *arr = ArrayCreate();
|
||||
|
@ -883,7 +878,7 @@ UserEncodePrivileges(int privileges)
|
|||
#undef A
|
||||
|
||||
finish:
|
||||
return JsonValueArray(arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
UserId *
|
||||
|
|
|
@ -42,54 +42,7 @@
|
|||
#include <Cytoplasm/Db.h>
|
||||
#include <Cytoplasm/Int64.h>
|
||||
|
||||
/**
|
||||
* This structure describes a registration token that is in the
|
||||
* database.
|
||||
*/
|
||||
typedef struct RegTokenInfo
|
||||
{
|
||||
Db *db;
|
||||
DbRef *ref;
|
||||
|
||||
/*
|
||||
* The token itself.
|
||||
*/
|
||||
char *name;
|
||||
|
||||
/*
|
||||
* Who created this token. Note that this can be NULL if the
|
||||
* token was created by Telodendria itself.
|
||||
*/
|
||||
char *owner;
|
||||
|
||||
/*
|
||||
* How many times the token was used.
|
||||
*/
|
||||
Int64 used;
|
||||
|
||||
/*
|
||||
* How many uses are allowed.
|
||||
*/
|
||||
Int64 uses;
|
||||
|
||||
/*
|
||||
* Timestamp when this token was created.
|
||||
*/
|
||||
UInt64 created;
|
||||
|
||||
/*
|
||||
* Timestamp when this token expires, or 0 if it does not
|
||||
* expire.
|
||||
*/
|
||||
UInt64 expires;
|
||||
|
||||
/*
|
||||
* A bit field describing the privileges this token grants. See
|
||||
* the User API documentation for the privileges supported.
|
||||
*/
|
||||
int grants;
|
||||
|
||||
} RegTokenInfo;
|
||||
#include <Schema/RegToken.h>
|
||||
|
||||
/**
|
||||
* ``Use'' the specified registration token by increasing the used
|
||||
|
@ -132,7 +85,6 @@ RegTokenCreate(Db *, char *, char *, UInt64, Int64, int);
|
|||
* .Fn RegTokenClose .
|
||||
*/
|
||||
extern void RegTokenFree(RegTokenInfo *);
|
||||
|
||||
/**
|
||||
* Return a boolean value indicating whether or not the specified token
|
||||
* is valid. A registration token is only valid if it has not expired
|
||||
|
|
|
@ -105,6 +105,8 @@ ROUTE(RouteRoomAliases);
|
|||
|
||||
ROUTE(RouteAdminDeactivate);
|
||||
|
||||
ROUTE(RouteAdminTokens);
|
||||
|
||||
#undef ROUTE
|
||||
|
||||
#endif
|
||||
|
|
|
@ -286,13 +286,13 @@ extern int UserSetPrivileges(User *, int);
|
|||
* Decode the JSON that represents the user privileges into a packed
|
||||
* bit field for simple manipulation.
|
||||
*/
|
||||
extern int UserDecodePrivileges(JsonValue *);
|
||||
extern int UserDecodePrivileges(Array *);
|
||||
|
||||
/**
|
||||
* Encode the packed bit field that represents user privileges as a
|
||||
* JSON value.
|
||||
*/
|
||||
extern JsonValue *UserEncodePrivileges(int);
|
||||
extern Array *UserEncodePrivileges(int);
|
||||
|
||||
/**
|
||||
* Convert a string privilege into its bit in the bit field. This is
|
||||
|
|
Loading…
Reference in a new issue