forked from Telodendria/Telodendria
Compare commits
No commits in common. "38dfb2a505b0fa812d068f11d15949e297c349e8" and "263bcb9e161d83e3e4e536377a42276279e8beaf" have entirely different histories.
38dfb2a505
...
263bcb9e16
51 changed files with 1118 additions and 950 deletions
25
README.md
25
README.md
|
@ -1,6 +1,7 @@
|
||||||
<h1 style="text-align: center;">Lightweight, Decentralized Chat.</h1>
|
<p align="center"><img src="https://telodendria.io/assets/Telodendria-500x500.png"></p>
|
||||||
|
<h1 align="center">Telodendria</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
|
chat server designed to be easy to install and configure. Powered by
|
||||||
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
the [Matrix](https://matrix.org) protocol, Telodendria empowers
|
||||||
everyone to run their own chat server on ordinary hardware, including
|
everyone to run their own chat server on ordinary hardware, including
|
||||||
|
@ -11,7 +12,7 @@ hosting a complicated, high-maintenance homeserver or joining an
|
||||||
existing homeserver for privacy or other reasons, then Telodendria
|
existing homeserver for privacy or other reasons, then Telodendria
|
||||||
might be for you.
|
might be for you.
|
||||||
|
|
||||||
!!!! **Note:** Telodendria still in development. See **Status** below.
|
> **Note:** Telodendria still in development. See [Status](#status).
|
||||||
|
|
||||||
## What is Matrix?
|
## What is Matrix?
|
||||||
|
|
||||||
|
@ -63,13 +64,12 @@ incredibly outdated. Telodendria, on the other hand, aims to be stable.
|
||||||
It should *just work* for long periods of time between upgrades, and
|
It should *just work* for long periods of time between upgrades, and
|
||||||
you should never feel like Telodendria is going to change significantly
|
you should never feel like Telodendria is going to change significantly
|
||||||
between upgrades.
|
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 →](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/dev/rationale.md)
|
[Read Technical Rationale →](docs/dev/rationale.md)
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
Check out the [Documentation](https://git.telodendria.io/Telodendria/telodendria/src/branch/master/docs/README.md) to get started with
|
Check out the [Documentation](docs/README.md) to get started with
|
||||||
Telodendria.
|
Telodendria.
|
||||||
|
|
||||||
## Status
|
## 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
|
ready for end-users yet. While it features very basic user
|
||||||
authentication, it does not actually work as a chat server yet.
|
authentication, it does not actually work as a chat server yet.
|
||||||
|
|
||||||
We are hoping to ship Telodendria `v1.7.0-alpha4` by January of 2025. This
|
We are hoping to ship Telodendria `v0.4.0` by May of 2024. This
|
||||||
release should be usable for communication between **local users**
|
release should be usable for communication between **local users**
|
||||||
only. Additional features, including federation with other Matrix
|
only. Additional features, including federation with other Matrix
|
||||||
homeservers will be added in future releases.
|
homeservers will be added in future releases.
|
||||||
|
|
||||||
You can help speed up development by **sponsoring**
|
You can help speed up development by [sponsoring](#sponsorship)
|
||||||
Telodendria or [getting involved](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/docs/CONTRIBUTING.md).
|
Telodendria or [getting involved](docs/CONTRIBUTING.md).
|
||||||
|
|
||||||
## Sponsorship
|
## Sponsorship
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ Telodendria's long-term success, please consider sponsoring the
|
||||||
project.
|
project.
|
||||||
|
|
||||||
You can make a recurring donation to Telodendria using
|
You can make a recurring donation to Telodendria using
|
||||||
[LiberaPay](https://liberapay.com/Telodendria/donate). You can also make
|
[LiberaPay](https://bancino.net/Telodendria/donate). You can also make
|
||||||
one-time donations using
|
one-time donations using
|
||||||
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
[Stripe](https://donate.stripe.com/8wM29AfF5bRJc48eUU). If you would
|
||||||
like to make a recurring donation larger than that allowed by
|
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
|
While there are no set sponsorship tiers at this time, sponsoring
|
||||||
Telodendria is a mutually beneficial relationship. Depending on the
|
Telodendria is a mutually beneficial relationship. Depending on the
|
||||||
amount you donate, you can get your name, logo, and website links
|
amount you donate, you can get your name, logo, and website links
|
||||||
on the [Sponsors](../sponsors) page, the project `README`, or the
|
on the [Sponsors](docs/SPONSORS.md) page, the project `README`, or the
|
||||||
main website.
|
main website.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -116,7 +116,7 @@ main website.
|
||||||
All of the code and documentation for Telodendria is licensed under a
|
All of the code and documentation for Telodendria is licensed under a
|
||||||
modified MIT license. The MIT license is an extremely permissive
|
modified MIT license. The MIT license is an extremely permissive
|
||||||
license that has very few restrictions. Please consult the
|
license that has very few restrictions. Please consult the
|
||||||
[`LICENSE.txt`](https://git.telodendria.io/Telodendria/Telodendria/src/branch/master/LICENSE.txt) file for the actual license text. It is
|
[`LICENSE.txt`](LICENSE.txt) file for the actual license text. It is
|
||||||
important to note that the Telodendria license text differs from the
|
important to note that the Telodendria license text differs from the
|
||||||
original MIT license in the following ways:
|
original MIT license in the following ways:
|
||||||
|
|
||||||
|
@ -133,3 +133,4 @@ 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
|
official project. If Telodendria is forked, the logo must be removed
|
||||||
completely from the project, and optionally replaced by a different
|
completely from the project, and optionally replaced by a different
|
||||||
one.
|
one.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"guard": "TELODENDRIA_SCHEMA_FILTER_H",
|
|
||||||
"header": "Schema\/Filter.h",
|
"header": "Schema\/Filter.h",
|
||||||
"types": {
|
"types": {
|
||||||
"FilterRoom": {
|
"FilterRoom": {
|
||||||
|
@ -117,5 +116,6 @@
|
||||||
},
|
},
|
||||||
"type": "struct"
|
"type": "struct"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"guard": "TELODENDRIA_SCHEMA_FILTER_H"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
TOOLS="tools/src"
|
||||||
SCHEMA="Schema"
|
SCHEMA="Schema"
|
||||||
|
|
||||||
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE} -I${BUILD}"
|
CFLAGS="-Wall -Wextra -pedantic -std=c89 -O3 -pipe -D_DEFAULT_SOURCE -I${INCLUDE}"
|
||||||
LIBS="-lm -pthread -lCytoplasm"
|
LIBS="-lm -pthread -lCytoplasm"
|
||||||
|
|
||||||
|
|
||||||
# Set default args for all platforms
|
# Set default args for all platforms
|
||||||
SCRIPT_ARGS="--cc=cc --prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=1.7.0-alpha4 --static $@"
|
SCRIPT_ARGS="--prefix=/usr/local --enable-ld-extra --bin-name=telodendria --version=0.4.0 --static $@"
|
||||||
|
|
||||||
echo "Processing options..."
|
echo "Processing options..."
|
||||||
echo "Ran with arguments: $SCRIPT_ARGS"
|
echo "Ran with arguments: $SCRIPT_ARGS"
|
||||||
|
@ -27,9 +27,6 @@ echo "Ran with arguments: $SCRIPT_ARGS"
|
||||||
# Process all arguments
|
# Process all arguments
|
||||||
for arg in $SCRIPT_ARGS; do
|
for arg in $SCRIPT_ARGS; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--cc=*)
|
|
||||||
CC=$(echo "$arg" | cut -d '=' -f 2-)
|
|
||||||
;;
|
|
||||||
--prefix=*)
|
--prefix=*)
|
||||||
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
PREFIX=$(echo "$arg" | cut -d '=' -f 2-)
|
||||||
;;
|
;;
|
||||||
|
@ -63,7 +60,7 @@ for arg in $SCRIPT_ARGS; do
|
||||||
STATIC=""
|
STATIC=""
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid argument: $arg"
|
echo "Invalid argument: $1"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@ -115,8 +112,8 @@ compile_obj() {
|
||||||
src="$1"
|
src="$1"
|
||||||
obj="$2"
|
obj="$2"
|
||||||
|
|
||||||
pref=$(${CC} -I${INCLUDE} -I${BUILD} -MM -MT "${obj}" "${src}")
|
pref=$(cc -I${INCLUDE} -MM -MT "${obj}" "${src}")
|
||||||
echo "$pref $(collect ${SCHEMA}/ .json .h ${BUILD}/Schema/ print_obj)"
|
echo "$pref $(collect ${SCHEMA}/ .json .h ${INCLUDE}/Schema/ print_obj)"
|
||||||
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
echo "${TAB}@mkdir -p $(dirname ${obj})"
|
||||||
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
|
||||||
}
|
}
|
||||||
|
@ -156,19 +153,13 @@ compile_schema() {
|
||||||
src="$1"
|
src="$1"
|
||||||
out="$2"
|
out="$2"
|
||||||
|
|
||||||
obj="${BUILD}/Schema/${out}.o"
|
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\""
|
||||||
|
|
||||||
echo "${BUILD}/Schema/${out}.h:"
|
echo "${SRC}/Schema/${out}.c:"
|
||||||
echo "${TAB}@mkdir -p ${BUILD}/Schema"
|
echo "${TAB}@mkdir -p ${INCLUDE}/Schema ${SRC}/Schema"
|
||||||
echo "${TAB}j2s -s \"${src}\" -h \"${BUILD}/Schema/${out}.h\" -c \"${BUILD}/Schema/${out}.c\""
|
echo "${TAB}j2s -s \"${src}\" -h \"${INCLUDE}/Schema/${out}.h\" -c \"${SRC}/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() {
|
install_out() {
|
||||||
|
@ -194,16 +185,22 @@ uninstall_out() {
|
||||||
|
|
||||||
echo "Generating Makefile..."
|
echo "Generating Makefile..."
|
||||||
|
|
||||||
OBJS="$(collect ${SRC}/ .c .o ${BUILD}/ print_obj) $(collect ${SCHEMA}/ .json .o ${BUILD}/Schema/ print_obj)"
|
OBJS=$(collect ${SRC}/ .c .o ${BUILD}/ print_obj)
|
||||||
TAB=$(printf '\t')
|
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
|
cat << EOF > Makefile
|
||||||
.POSIX:
|
.POSIX:
|
||||||
|
|
||||||
# Generated by '$0' on $(date).
|
# Generated by '$0' on $(date).
|
||||||
# This file should generally not be manually edited.
|
# This file should generally not be manually edited.
|
||||||
|
|
||||||
CC = ${CC}
|
CC = cc
|
||||||
PREFIX = ${PREFIX}
|
PREFIX = ${PREFIX}
|
||||||
CFLAGS = ${CFLAGS}
|
CFLAGS = ${CFLAGS}
|
||||||
LDFLAGS = ${LDFLAGS}
|
LDFLAGS = ${LDFLAGS}
|
||||||
|
|
|
@ -5,22 +5,12 @@ 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
|
reporting in the change log. As such, it changes frequently between releases. Final
|
||||||
change log entries are published as [Releases](releases).
|
change log entries are published as [Releases](releases).
|
||||||
|
|
||||||
## v1.7.0-alpha4
|
## v0.4.0
|
||||||
|
|
||||||
**Not Released Yet.**
|
**Not Released Yet.**
|
||||||
|
|
||||||
This release brings filters, rooms, and events! The core of the Matrix
|
This release brings filters, rooms, and events! The core of the Matrix protocol architecture
|
||||||
protocol architecture is now in place.
|
is not 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
|
### Matrix Specification
|
||||||
|
|
||||||
|
@ -31,30 +21,19 @@ The following endpoints were added:
|
||||||
|
|
||||||
### Bug Fixes & General Improvements
|
### Bug Fixes & General Improvements
|
||||||
|
|
||||||
- Fixed a double-free in `RouteUserProfile()` that would cause errors
|
- Fixed a double-free in `RouteUserProfile()` that would cause errors with certain
|
||||||
with certain Matrix clients. (#35)
|
Matrix clients. (#35)
|
||||||
- Improved compatibility with NetBSD on various platforms.
|
- Improved compatibility with NetBSD on various platforms.
|
||||||
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository. It
|
- Moved [Cytoplasm](/Telodendria/Cytoplasm) to its own repository.
|
||||||
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.
|
||||||
- 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
|
### New Features
|
||||||
|
|
||||||
- Moved all administrator API endpoints to `/_telodendria/admin/v1`,
|
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins to be able to
|
||||||
because later revisions of the administrator API may break clients, so
|
deactivate users.
|
||||||
we want a way to give those breaking revisions new endpoints.
|
- Moved all administrator API endpoints to `/_telodendria/admin/v1`, because later revisions
|
||||||
- Implemented `/_telodendria/admin/v1/deactivate/[localpart]` for admins
|
of the administrator API may break clients, so we want a way to give those breaking revisions
|
||||||
to be able to deactivate users.
|
new endpoints.
|
||||||
- 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
|
## v0.3.0
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ request.
|
||||||
- [Configuration](config.md)
|
- [Configuration](config.md)
|
||||||
- [Server Statistics](stats.md)
|
- [Server Statistics](stats.md)
|
||||||
- [Process Control](proc.md)
|
- [Process Control](proc.md)
|
||||||
- [Registration Tokens](tokens.md)
|
|
||||||
|
|
||||||
## API Conventions
|
## 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
|
intended to be managed via the configuration API. Consult the
|
||||||
[Configuration](../config.md) document for a complete list of supported
|
[Configuration](../config.md) document for a complete list of supported
|
||||||
configuration options. This document simply describes the API used to
|
configuration options. This document simply describes the API used to
|
||||||
update the configuration described in that document.
|
update the configuration.
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/v1/config`
|
### **GET** `/_telodendria/admin/config`
|
||||||
|
|
||||||
Retrieve the current configuration.
|
Retrieve the current configuration.
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ Retrieve the current configuration.
|
||||||
|---------------|-------------|
|
|---------------|-------------|
|
||||||
| 200 | The current configuration was successfully retrieved.|
|
| 200 | The current configuration was successfully retrieved.|
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/v1/config`
|
### **POST** `/_telodendria/admin/config`
|
||||||
|
|
||||||
Installs a new configuration. This endpoint validates the request body,
|
Installs a new configuration. This endpoint validates the request body,
|
||||||
ensuring it is a proper configuration, then it replaces the existing
|
ensuring it is a proper configuration, then it replaces the existing
|
||||||
|
@ -40,23 +40,3 @@ 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.
|
| `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.
|
The following API endpoints are implemented for managing privileges.
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/v1/privileges/[localpart]`
|
### **GET** `/_telodendria/admin/privileges/[localpart]`
|
||||||
|
|
||||||
Retrieve the permissions for a user. If the localpart is omitted, then
|
Retrieve the permissions for a user. If the localpart is omitted, then
|
||||||
retrieve the privileges for the user that owns the access token being
|
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.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/v1/privileges/[localpart]`
|
### **POST** `/_telodendria/admin/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by replacing the privileges array
|
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
|
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.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **PUT** `/_telodendria/admin/v1/privileges/[localpart]`
|
### **PUT** `/_telodendria/admin/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by adding the privileges
|
Update the privileges of a local user by adding the privileges
|
||||||
specified in the request to the users existing 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.|
|
| `privileges` | `Array` | An array of privileges, as described above. The privileges are encoded as JSON strings.|
|
||||||
|
|
||||||
### **DELETE** `/_telodendria/admin/v1/privileges/[localpart]`
|
### **DELETE** `/_telodendria/admin/privileges/[localpart]`
|
||||||
|
|
||||||
Update the privileges of a local user by removing the privileges
|
Update the privileges of a local user by removing the privileges
|
||||||
specified in the request from the user's existing privileges.
|
specified in the request from the user's existing privileges.
|
||||||
|
|
|
@ -5,7 +5,7 @@ administrator to manage the Telodendria process itself.
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/v1/restart`
|
### **POST** `/_telodendria/admin/restart`
|
||||||
|
|
||||||
Restart the Telodendria daemon cleanly. This endpoint will respond
|
Restart the Telodendria daemon cleanly. This endpoint will respond
|
||||||
immediately after signaling to the daemon that it should be restarted
|
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.
|
On success, this endpoint simply returns an empty JSON object.
|
||||||
|
|
||||||
### **POST** `/_telodendria/admin/v1/shutdown`
|
### **POST** `/_telodendria/admin/shutdown`
|
||||||
|
|
||||||
Shut down the Telodendria process cleanly. This endpoint will respond
|
Shut down the Telodendria process cleanly. This endpoint will respond
|
||||||
immediately after signalling to the daemon that it should be shut
|
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
|
## API Endpoints
|
||||||
|
|
||||||
### **GET** `/_telodendria/admin/v1/stats`
|
### **GET** `/_telodendria/admin/stats`
|
||||||
|
|
||||||
Retrieve basic statistics about the currently running Telodendria
|
Retrieve basic statistics about the currently running Telodendria
|
||||||
process.
|
process.
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
# 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
Normal file
138
site/index.html
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<!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
Normal file
368
site/mandoc.css
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
/* $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
Normal file
114
site/style.css
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
2
site/telodendria-signify.pub
Normal file
2
site/telodendria-signify.pub
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
untrusted comment: signify public key
|
||||||
|
RWTPPnWvnpee8NlygSggQqk5V5oghl6Ikq99bZl5IRQwiRMLaJnq82mw
|
|
@ -28,7 +28,5 @@
|
||||||
HashMap *
|
HashMap *
|
||||||
FilterApply(Filter * filter, HashMap * event)
|
FilterApply(Filter * filter, HashMap * event)
|
||||||
{
|
{
|
||||||
(void) filter;
|
|
||||||
(void) event;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,10 @@ start:
|
||||||
httpServers = NULL;
|
httpServers = NULL;
|
||||||
restart = 0;
|
restart = 0;
|
||||||
|
|
||||||
|
/* For getopt() */
|
||||||
|
opterr = 1;
|
||||||
|
optind = 1;
|
||||||
|
|
||||||
/* Local variables */
|
/* Local variables */
|
||||||
exit = EXIT_SUCCESS;
|
exit = EXIT_SUCCESS;
|
||||||
flags = 0;
|
flags = 0;
|
||||||
|
|
|
@ -30,10 +30,8 @@
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/Util.h>
|
#include <Cytoplasm/Util.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
|
||||||
#include <Cytoplasm/Log.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
|
#include <Cytoplasm/Int64.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
RegTokenValid(RegTokenInfo * token)
|
RegTokenValid(RegTokenInfo * token)
|
||||||
|
@ -101,7 +99,8 @@ RegTokenDelete(RegTokenInfo * token)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
RegTokenInfoFree(token);
|
Free(token->name);
|
||||||
|
Free(token->owner);
|
||||||
Free(token);
|
Free(token);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -114,8 +113,6 @@ RegTokenGetInfo(Db * db, char *token)
|
||||||
DbRef *tokenRef;
|
DbRef *tokenRef;
|
||||||
HashMap *tokenJson;
|
HashMap *tokenJson;
|
||||||
|
|
||||||
char *errp = NULL;
|
|
||||||
|
|
||||||
if (!RegTokenExists(db, token))
|
if (!RegTokenExists(db, token))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -129,43 +126,47 @@ RegTokenGetInfo(Db * db, char *token)
|
||||||
tokenJson = DbJson(tokenRef);
|
tokenJson = DbJson(tokenRef);
|
||||||
ret = Malloc(sizeof(RegTokenInfo));
|
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->db = db;
|
||||||
ret->ref = tokenRef;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
RegTokenFree(RegTokenInfo *tokeninfo)
|
RegTokenFree(RegTokenInfo * tokeninfo)
|
||||||
{
|
{
|
||||||
if (tokeninfo)
|
if (tokeninfo)
|
||||||
{
|
{
|
||||||
RegTokenInfoFree(tokeninfo);
|
Free(tokeninfo->name);
|
||||||
|
Free(tokeninfo->owner);
|
||||||
Free(tokeninfo);
|
Free(tokeninfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int
|
int
|
||||||
RegTokenClose(RegTokenInfo * tokeninfo)
|
RegTokenClose(RegTokenInfo * tokeninfo)
|
||||||
{
|
{
|
||||||
HashMap *json;
|
|
||||||
|
|
||||||
if (!tokeninfo)
|
if (!tokeninfo)
|
||||||
{
|
{
|
||||||
return 0;
|
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);
|
return DbUnlock(tokeninfo->db, tokeninfo->ref);
|
||||||
}
|
}
|
||||||
static int
|
static int
|
||||||
|
@ -201,6 +202,7 @@ RegTokenInfo *
|
||||||
RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int privileges)
|
RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int privileges)
|
||||||
{
|
{
|
||||||
RegTokenInfo *ret;
|
RegTokenInfo *ret;
|
||||||
|
HashMap *tokenJson;
|
||||||
|
|
||||||
UInt64 timestamp = UtilServerTs();
|
UInt64 timestamp = UtilServerTs();
|
||||||
|
|
||||||
|
@ -233,12 +235,26 @@ RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ret->name = StrDuplicate(name);
|
ret->name = StrDuplicate(name);
|
||||||
ret->created_by = StrDuplicate(owner);
|
ret->owner = StrDuplicate(owner);
|
||||||
ret->used = Int64Create(0, 0);
|
ret->used = Int64Create(0, 0);
|
||||||
ret->uses = uses;
|
ret->uses = uses;
|
||||||
ret->created_on = timestamp;
|
ret->created = timestamp;
|
||||||
ret->expires_on = expires;
|
ret->expires = expires;
|
||||||
ret->grants = UserEncodePrivileges(privileges);
|
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));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,6 @@ struct Room
|
||||||
Room *
|
Room *
|
||||||
RoomCreate(Db * db, RoomCreateRequest * req)
|
RoomCreate(Db * db, RoomCreateRequest * req)
|
||||||
{
|
{
|
||||||
(void) db;
|
|
||||||
(void) req;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,6 @@ RouterBuild(void)
|
||||||
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
R("/_telodendria/admin/v1/privileges", RoutePrivileges);
|
||||||
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
R("/_telodendria/admin/v1/privileges/(.*)", RoutePrivileges);
|
||||||
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
R("/_telodendria/admin/v1/deactivate/(.*)", RouteAdminDeactivate);
|
||||||
R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens);
|
|
||||||
R("/_telodendria/admin/v1/tokens", RouteAdminTokens);
|
|
||||||
|
|
||||||
#undef R
|
#undef R
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
|
#include <Cytoplasm/Log.h>
|
||||||
|
|
||||||
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
ROUTE_IMPL(RouteAdminDeactivate, path, argp)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
/*
|
|
||||||
* 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,8 +65,6 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
char *token;
|
char *token;
|
||||||
char *newPassword;
|
char *newPassword;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
Config *config = ConfigLock(db);
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
|
@ -80,9 +78,8 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
msg = "Route only supports POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +118,9 @@ ROUTE_IMPL(RouteChangePwd, path, argp)
|
||||||
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
newPassword = JsonValueAsString(HashMapGet(request, "new_password"));
|
||||||
if (!newPassword)
|
if (!newPassword)
|
||||||
{
|
{
|
||||||
msg = "'new_password' is unset or not a string.";
|
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,14 +33,12 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
char *token;
|
char *token;
|
||||||
char *msg;
|
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
Config *config = NULL;
|
Config *config = NULL;
|
||||||
|
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
Config *newConf;
|
Config *newConf;
|
||||||
HashMap *newJson = NULL;
|
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
|
@ -60,19 +58,17 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_CONFIG))
|
if (!(UserGetPrivileges(user) & USER_CONFIG))
|
||||||
{
|
{
|
||||||
msg = "User does not have the 'CONFIG' privilege.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
config = ConfigLock(args->matrixArgs->db);
|
config = ConfigLock(args->matrixArgs->db);
|
||||||
if (!config)
|
if (!config)
|
||||||
{
|
{
|
||||||
msg = "Internal server error while locking configuration.";
|
|
||||||
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
|
Log(LOG_ERR, "Config endpoint failed to lock configuration.");
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,9 +89,8 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
newConf = ConfigParse(request);
|
newConf = ConfigParse(request);
|
||||||
if (!newConf)
|
if (!newConf)
|
||||||
{
|
{
|
||||||
msg = "Internal server error while parsing config.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +107,8 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
msg = "Internal server error while writing the config.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -127,59 +121,10 @@ ROUTE_IMPL(RouteConfig, path, argp)
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
break;
|
break;
|
||||||
case HTTP_PUT:
|
case HTTP_PUT:
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
/* TODO: Support incremental changes to the config */
|
||||||
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:
|
default:
|
||||||
msg = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,9 +41,8 @@ ROUTE_IMPL(RouteCreateRoom, path, argp)
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
err = "Unknown request method.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, err);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, "Unknown request method.");
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,6 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
Config *config = ConfigLock(db);
|
Config *config = ConfigLock(db);
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
|
@ -61,9 +59,8 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
msg = "Route only accepts POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,9 +128,8 @@ ROUTE_IMPL(RouteDeactivate, path, argp)
|
||||||
|
|
||||||
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
|
if (!UserDeleteTokens(user, NULL) || !UserDeactivate(user, NULL, NULL))
|
||||||
{
|
{
|
||||||
msg = "Internal server error: couldn't remove user properly.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,6 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
|
|
||||||
char *userParam = ArrayGet(path, 0);
|
char *userParam = ArrayGet(path, 0);
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!userParam)
|
if (!userParam)
|
||||||
{
|
{
|
||||||
/* Should be impossible */
|
/* Should be impossible */
|
||||||
|
@ -89,17 +87,15 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
id = UserIdParse(userParam, serverName);
|
id = UserIdParse(userParam, serverName);
|
||||||
if (!id)
|
if (!id)
|
||||||
{
|
{
|
||||||
msg = "Invalid user ID.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StrEquals(id->server, serverName))
|
if (!StrEquals(id->server, serverName))
|
||||||
{
|
{
|
||||||
msg = "Cannot use /filter for non-local users.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||||
response = MatrixErrorCreate(M_UNAUTHORIZED, msg);
|
response = MatrixErrorCreate(M_UNAUTHORIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,9 +115,8 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
|
|
||||||
if (!StrEquals(id->localpart, UserGetName(user)))
|
if (!StrEquals(id->localpart, UserGetName(user)))
|
||||||
{
|
{
|
||||||
msg = "Unauthorized to use /filter.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
HttpResponseStatus(args->context, HTTP_UNAUTHORIZED);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,9 +126,8 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
|
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
msg = "The filter for this user was not found.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,9 +161,8 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
filterId = StrRandom(12);
|
filterId = StrRandom(12);
|
||||||
if (!filterId)
|
if (!filterId)
|
||||||
{
|
{
|
||||||
msg = "Couldn't generate random filter ID; this is unintended.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,9 +170,8 @@ ROUTE_IMPL(RouteFilter, path, argp)
|
||||||
if (!ref)
|
if (!ref)
|
||||||
{
|
{
|
||||||
Free(filterId);
|
Free(filterId);
|
||||||
msg = "Couldn't write filter to the database, this is unintended.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <Schema/LoginRequest.h>
|
|
||||||
|
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
#include <Cytoplasm/HashMap.h>
|
#include <Cytoplasm/HashMap.h>
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
|
@ -45,9 +43,12 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
HashMap *identifier;
|
HashMap *identifier;
|
||||||
|
|
||||||
LoginRequest loginRequest;
|
char *deviceId = NULL;
|
||||||
LoginRequestUserIdentifier userIdentifier;
|
char *initialDeviceDisplayName = NULL;
|
||||||
|
int refreshToken = 0;
|
||||||
|
|
||||||
|
char *password;
|
||||||
|
char *type;
|
||||||
UserId *userId = NULL;
|
UserId *userId = NULL;
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
@ -56,14 +57,6 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
UserLoginInfo *loginInfo;
|
UserLoginInfo *loginInfo;
|
||||||
char *fullUsername;
|
char *fullUsername;
|
||||||
|
|
||||||
char *type;
|
|
||||||
char *initialDeviceDisplayName;
|
|
||||||
char *deviceId;
|
|
||||||
char *password;
|
|
||||||
int refreshToken;
|
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
Config *config = ConfigLock(db);
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
|
@ -75,9 +68,6 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
memset(&loginRequest, 0, sizeof(LoginRequest));
|
|
||||||
memset(&userIdentifier, 0, sizeof(LoginRequestUserIdentifier));
|
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
|
@ -98,27 +88,9 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LoginRequestFromJson(request, &loginRequest, &msg))
|
val = HashMapGet(request, "type");
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginRequest.type != REQUEST_TYPE_PASSWORD)
|
|
||||||
{
|
|
||||||
msg = "Unsupported login type.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
identifier = loginRequest.identifier;
|
|
||||||
|
|
||||||
val = HashMapGet(identifier, "type");
|
|
||||||
if (!val)
|
if (!val)
|
||||||
{
|
{
|
||||||
msg = "No login identifier type set.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
break;
|
break;
|
||||||
|
@ -126,60 +98,152 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_STRING)
|
if (JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "Invalid login identifier type.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
|
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);
|
||||||
|
|
||||||
|
val = HashMapGet(identifier, "type");
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
type = JsonValueAsString(val);
|
type = JsonValueAsString(val);
|
||||||
if (!StrEquals(type, "m.id.user"))
|
if (!StrEquals(type, "m.id.user"))
|
||||||
{
|
{
|
||||||
msg = "Invalid login identifier type.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!LoginRequestUserIdentifierFromJson(identifier,
|
|
||||||
&userIdentifier, &msg))
|
val = HashMapGet(identifier, "user");
|
||||||
|
if (!val)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JsonValueType(val) != JSON_STRING)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
userId = UserIdParse(userIdentifier.user, config->serverName);
|
userId = UserIdParse(JsonValueAsString(val), config->serverName);
|
||||||
if (!userId)
|
if (!userId)
|
||||||
{
|
{
|
||||||
msg = "Invalid user ID.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StrEquals(userId->server, config->serverName)
|
if (!StrEquals(userId->server, config->serverName)
|
||||||
|| !UserExists(db, userId->localpart))
|
|| !UserExists(db, userId->localpart))
|
||||||
{
|
{
|
||||||
msg = "Unknown user ID.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceId = loginRequest.device_id;
|
|
||||||
|
|
||||||
initialDeviceDisplayName =loginRequest.initial_device_display_name;
|
val = HashMapGet(request, "device_id");
|
||||||
password = loginRequest.password;
|
if (val)
|
||||||
refreshToken = loginRequest.refresh_token;
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
user = UserLock(db, userId->localpart);
|
user = UserLock(db, userId->localpart);
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
msg = "Couldn't lock user.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,11 +261,10 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
if (!loginInfo)
|
if (!loginInfo)
|
||||||
{
|
{
|
||||||
msg = "Invalid creditentials for user.";
|
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,9 +300,8 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
msg = "Route only accepts GET and POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,8 +309,5 @@ ROUTE_IMPL(RouteLogin, path, argp)
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
ConfigUnlock(config);
|
ConfigUnlock(config);
|
||||||
|
|
||||||
LoginRequestFree(&loginRequest);
|
|
||||||
LoginRequestUserIdentifierFree(&userIdentifier);
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,17 +38,14 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
||||||
|
|
||||||
char *tokenstr;
|
char *tokenstr;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user;
|
User *user;
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
msg = "This route only accepts POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &tokenstr);
|
response = MatrixGetAccessToken(args->context, &tokenstr);
|
||||||
|
@ -87,9 +84,8 @@ ROUTE_IMPL(RouteLogout, path, argp)
|
||||||
{
|
{
|
||||||
if (!UserDeleteToken(user, tokenstr))
|
if (!UserDeleteToken(user, tokenstr))
|
||||||
{
|
{
|
||||||
msg = "Internal server error: couldn't delete token.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,6 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
JsonValue *val;
|
JsonValue *val;
|
||||||
int privileges;
|
int privileges;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
if (response)
|
if (response)
|
||||||
{
|
{
|
||||||
|
@ -57,9 +55,8 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
|
if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES))
|
||||||
{
|
{
|
||||||
msg = "User doesn't have the GRANT_PRIVILEGES privilege";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,9 +68,8 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
|
user = UserLock(args->matrixArgs->db, ArrayGet(path, 0));
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
msg = "Unknown user.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,24 +90,23 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
val = HashMapGet(request, "privileges");
|
val = HashMapGet(request, "privileges");
|
||||||
if (!val || JsonValueType(val) != JSON_ARRAY)
|
if (!val || JsonValueType(val) != JSON_ARRAY)
|
||||||
{
|
{
|
||||||
msg = "'privileges' is unset or not an array.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_POST:
|
case HTTP_POST:
|
||||||
privileges = UserDecodePrivileges(JsonValueAsArray(val));
|
privileges = UserDecodePrivileges(val);
|
||||||
break;
|
break;
|
||||||
case HTTP_PUT:
|
case HTTP_PUT:
|
||||||
privileges = UserGetPrivileges(user);
|
privileges = UserGetPrivileges(user);
|
||||||
privileges |= UserDecodePrivileges(JsonValueAsArray(val));
|
privileges |= UserDecodePrivileges(val);
|
||||||
break;
|
break;
|
||||||
case HTTP_DELETE:
|
case HTTP_DELETE:
|
||||||
privileges = UserGetPrivileges(user);
|
privileges = UserGetPrivileges(user);
|
||||||
privileges &= ~UserDecodePrivileges(JsonValueAsArray(val));
|
privileges &= ~UserDecodePrivileges(val);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* Impossible */
|
/* Impossible */
|
||||||
|
@ -121,21 +116,19 @@ ROUTE_IMPL(RoutePrivileges, path, argp)
|
||||||
|
|
||||||
if (!UserSetPrivileges(user, privileges))
|
if (!UserSetPrivileges(user, privileges))
|
||||||
{
|
{
|
||||||
msg = "Internal server error: couldn't set privileges.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fall through */
|
/* Fall through */
|
||||||
case HTTP_GET:
|
case HTTP_GET:
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
HashMapSet(response, "privileges", JsonValueArray(UserEncodePrivileges(UserGetPrivileges(user))));
|
HashMapSet(response, "privileges", UserEncodePrivileges(UserGetPrivileges(user)));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
msg = "Route only accepts POST, PUT, DELETE, and GET.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
char *op = ArrayGet(path, 0);
|
char *op = ArrayGet(path, 0);
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
char *token;
|
char *token;
|
||||||
char *msg;
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
response = MatrixGetAccessToken(args->context, &token);
|
response = MatrixGetAccessToken(args->context, &token);
|
||||||
|
@ -56,13 +55,11 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
|
|
||||||
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
|
if (!(UserGetPrivileges(user) & USER_PROC_CONTROL))
|
||||||
{
|
{
|
||||||
msg = "User doesn't have PROC_CONTROL privilege.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = "Unknown operation.";
|
|
||||||
switch (HttpRequestMethodGet(args->context))
|
switch (HttpRequestMethodGet(args->context))
|
||||||
{
|
{
|
||||||
case HTTP_POST:
|
case HTTP_POST:
|
||||||
|
@ -77,7 +74,7 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -109,12 +106,12 @@ ROUTE_IMPL(RouteProcControl, path, argp)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,6 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
UserAccessToken *newAccessToken;
|
UserAccessToken *newAccessToken;
|
||||||
char *deviceId;
|
char *deviceId;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
@ -57,9 +55,8 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
msg = "This route only accepts POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -72,9 +69,8 @@ ROUTE_IMPL(RouteRefresh, path, argp)
|
||||||
val = HashMapGet(request, "refresh_token");
|
val = HashMapGet(request, "refresh_token");
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "'refresh_token' is unset or not a string.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,6 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Memory.h>
|
#include <Cytoplasm/Memory.h>
|
||||||
|
|
||||||
#include <Schema/Registration.h>
|
|
||||||
|
|
||||||
#include <User.h>
|
#include <User.h>
|
||||||
#include <Uia.h>
|
#include <Uia.h>
|
||||||
#include <RegToken.h>
|
#include <RegToken.h>
|
||||||
|
@ -57,15 +55,22 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
HashMap *request = NULL;
|
HashMap *request = NULL;
|
||||||
HashMap *response = NULL;
|
HashMap *response = NULL;
|
||||||
|
|
||||||
RegistrationRequest regReq;
|
JsonValue *val;
|
||||||
|
|
||||||
char *kind;
|
char *kind;
|
||||||
|
|
||||||
|
char *username = NULL;
|
||||||
|
char *password = NULL;
|
||||||
|
char *initialDeviceDisplayName = NULL;
|
||||||
|
int refreshToken = 0;
|
||||||
|
int inhibitLogin = 0;
|
||||||
|
char *deviceId = NULL;
|
||||||
char *fullUsername;
|
char *fullUsername;
|
||||||
char *msg;
|
|
||||||
char *username;
|
|
||||||
|
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
|
|
||||||
User *user = NULL;
|
User *user = NULL;
|
||||||
|
|
||||||
Array *uiaFlows = NULL;
|
Array *uiaFlows = NULL;
|
||||||
int uiaResult;
|
int uiaResult;
|
||||||
|
|
||||||
|
@ -74,22 +79,11 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
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)
|
if (!config)
|
||||||
{
|
{
|
||||||
msg = "Internal server error while locking configuration.";
|
|
||||||
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
|
Log(LOG_ERR, "Registration endpoint failed to lock configuration.");
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ArraySize(path) == 0)
|
if (ArraySize(path) == 0)
|
||||||
|
@ -108,23 +102,26 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
response = MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
if (!RegistrationRequestFromJson(request, ®Req, &msg))
|
|
||||||
{
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
|
||||||
response = MatrixErrorCreate(M_NOT_JSON, msg);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regReq.username)
|
val = HashMapGet(request, "username");
|
||||||
|
if (val)
|
||||||
{
|
{
|
||||||
if (!UserValidate(regReq.username, config->serverName))
|
if (JsonValueType(val) != JSON_STRING)
|
||||||
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
username = StrDuplicate(JsonValueAsString(val));
|
||||||
|
|
||||||
|
if (!UserValidate(username, config->serverName))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
response = MatrixErrorCreate(M_INVALID_USERNAME, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UserExists(db, regReq.username))
|
if (UserExists(db, username))
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
|
response = MatrixErrorCreate(M_USER_IN_USE, NULL);
|
||||||
|
@ -161,44 +158,99 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
/* We don't support guest accounts yet */
|
/* We don't support guest accounts yet */
|
||||||
if (kind && !StrEquals(kind, "user"))
|
if (kind && !StrEquals(kind, "user"))
|
||||||
{
|
{
|
||||||
msg = "Guest accounts are currently not supported";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!regReq.password)
|
val = HashMapGet(request, "password");
|
||||||
|
if (!val)
|
||||||
{
|
{
|
||||||
msg = "'password' field is unset";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
|
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All of the other fields are optional, we don't have to check
|
if (JsonValueType(val) != JSON_STRING)
|
||||||
* them. */
|
{
|
||||||
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
|
||||||
user = UserCreate(db, regReq.username, regReq.password);
|
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);
|
||||||
response = HashMapCreate();
|
response = HashMapCreate();
|
||||||
|
|
||||||
fullUsername = StrConcat(4,
|
fullUsername = StrConcat(4, "@", UserGetName(user), ":", config->serverName);
|
||||||
"@", UserGetName(user), ":", config->serverName);
|
|
||||||
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
HashMapSet(response, "user_id", JsonValueString(fullUsername));
|
||||||
Free(fullUsername);
|
Free(fullUsername);
|
||||||
|
|
||||||
HttpResponseStatus(args->context, HTTP_OK);
|
HttpResponseStatus(args->context, HTTP_OK);
|
||||||
if (!regReq.inhibit_login)
|
if (!inhibitLogin)
|
||||||
{
|
{
|
||||||
UserLoginInfo *loginInfo = UserLogin(user, regReq.password,
|
UserLoginInfo *loginInfo = UserLogin(user, password, deviceId,
|
||||||
regReq.device_id, regReq.initial_device_display_name,
|
initialDeviceDisplayName, refreshToken);
|
||||||
regReq.refresh_token);
|
|
||||||
|
|
||||||
HashMapSet(response, "access_token",
|
HashMapSet(response, "access_token",
|
||||||
JsonValueString(loginInfo->accessToken->string));
|
JsonValueString(loginInfo->accessToken->string));
|
||||||
HashMapSet(response, "device_id",
|
HashMapSet(response, "device_id",
|
||||||
JsonValueString(loginInfo->accessToken->deviceId));
|
JsonValueString(loginInfo->accessToken->deviceId));
|
||||||
|
|
||||||
if (regReq.refresh_token)
|
if (refreshToken)
|
||||||
{
|
{
|
||||||
HashMapSet(response, "expires_in_ms",
|
HashMapSet(response, "expires_in_ms",
|
||||||
JsonValueInteger(loginInfo->accessToken->lifetime));
|
JsonValueInteger(loginInfo->accessToken->lifetime));
|
||||||
|
@ -224,7 +276,7 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
|
|
||||||
if (info)
|
if (info)
|
||||||
{
|
{
|
||||||
UserSetPrivileges(user, UserDecodePrivileges(info->grants));
|
UserSetPrivileges(user, info->grants);
|
||||||
RegTokenClose(info);
|
RegTokenClose(info);
|
||||||
RegTokenFree(info);
|
RegTokenFree(info);
|
||||||
}
|
}
|
||||||
|
@ -242,7 +294,10 @@ ROUTE_IMPL(RouteRegister, path, argp)
|
||||||
UserUnlock(user);
|
UserUnlock(user);
|
||||||
finish:
|
finish:
|
||||||
UiaFlowsFree(uiaFlows);
|
UiaFlowsFree(uiaFlows);
|
||||||
RegistrationRequestFree(®Req);
|
Free(username);
|
||||||
|
Free(password);
|
||||||
|
Free(deviceId);
|
||||||
|
Free(initialDeviceDisplayName);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -255,9 +310,8 @@ finish:
|
||||||
|
|
||||||
if (!username)
|
if (!username)
|
||||||
{
|
{
|
||||||
msg = "'username' path parameter is not set.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_MISSING_PARAM, msg);
|
response = MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
}
|
}
|
||||||
else if (!UserValidate(username, config->serverName))
|
else if (!UserValidate(username, config->serverName))
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,37 +26,19 @@
|
||||||
#include <Cytoplasm/Str.h>
|
#include <Cytoplasm/Str.h>
|
||||||
#include <Cytoplasm/Json.h>
|
#include <Cytoplasm/Json.h>
|
||||||
|
|
||||||
#include <Schema/RequestToken.h>
|
|
||||||
|
|
||||||
ROUTE_IMPL(RouteRequestToken, path, argp)
|
ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
{
|
{
|
||||||
RouteArgs *args = argp;
|
RouteArgs *args = argp;
|
||||||
char *type = ArrayGet(path, 0);
|
char *type = ArrayGet(path, 0);
|
||||||
HashMap *request;
|
HashMap *request;
|
||||||
HashMap *response;
|
HashMap *response;
|
||||||
|
JsonValue *val;
|
||||||
char *msg;
|
char *str;
|
||||||
|
|
||||||
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)
|
if (HttpRequestMethodGet(args->context) != HTTP_POST)
|
||||||
{
|
{
|
||||||
msg = "This route only accepts POST.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -66,92 +48,87 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
return MatrixErrorCreate(M_NOT_JSON, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!RequestTokenFromJson(request, &reqTok, &msg))
|
val = HashMapGet(request, "client_secret");
|
||||||
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reqTok.client_secret)
|
str = JsonValueAsString(val);
|
||||||
|
if (strlen(str) > 255 || StrBlank(str))
|
||||||
{
|
{
|
||||||
msg = "'client_secret' is not set";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(reqTok.client_secret) > 255 || StrBlank(reqTok.client_secret))
|
val = HashMapGet(request, "send_attempt");
|
||||||
|
if (!val || JsonValueType(val) != JSON_INTEGER)
|
||||||
{
|
{
|
||||||
msg = "'client_secret' is blank or too long";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Int64Eq(reqTok.send_attempt, minusOne))
|
val = HashMapGet(request, "next_link");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "Invalid or inexistent 'send_attempt'";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reqTok.next_link)
|
val = HashMapGet(request, "id_access_token");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "'next_link' is not set";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
if (!reqTok.id_access_token)
|
|
||||||
|
val = HashMapGet(request, "id_server");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "'id_access_token' is not set";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
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;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrEquals(type, "email"))
|
if (StrEquals(type, "email"))
|
||||||
{
|
{
|
||||||
if (!reqTok.email)
|
val = HashMapGet(request, "email");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "Type is set to 'email' yet none was set";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (StrEquals(type, "msisdn"))
|
else if (StrEquals(type, "msisdn"))
|
||||||
{
|
{
|
||||||
if (!reqTok.country)
|
val = HashMapGet(request, "country");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "Type is set to 'msisdn' yet no country is set";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(reqTok.country) != 2)
|
str = JsonValueAsString(val);
|
||||||
|
if (strlen(str) != 2)
|
||||||
{
|
{
|
||||||
msg = "Invalid country tag, length must be 2";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reqTok.phone_number)
|
val = HashMapGet(request, "phone_number");
|
||||||
|
if (val && JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "Type is set to 'msisdn' yet phone_number is unset";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_BAD_JSON, msg);
|
response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,6 +145,5 @@ ROUTE_IMPL(RouteRequestToken, path, argp)
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
RequestTokenFree(&reqTok);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,6 @@ ROUTE_IMPL(RouteRoomAliases, path, argp)
|
||||||
Db *db = args->matrixArgs->db;
|
Db *db = args->matrixArgs->db;
|
||||||
DbRef *ref = NULL;
|
DbRef *ref = NULL;
|
||||||
|
|
||||||
(void) roomId;
|
|
||||||
|
|
||||||
/* TODO: Placeholder; remove. */
|
|
||||||
goto finish;
|
|
||||||
finish:
|
finish:
|
||||||
DbUnlock(db, ref);
|
DbUnlock(db, ref);
|
||||||
JsonFree(request);
|
JsonFree(request);
|
||||||
|
|
|
@ -47,8 +47,8 @@ ROUTE_IMPL(RouteStaticResources, path, argp)
|
||||||
"function findGetParameter(parameterName) {"
|
"function findGetParameter(parameterName) {"
|
||||||
" var result = null;"
|
" var result = null;"
|
||||||
" var tmp = [];"
|
" var tmp = [];"
|
||||||
" var items = location.search.substr(1).split(\"&\");"
|
" var items = location.search.substr(1).split(\"&\");"
|
||||||
" for (var index = 0; index < items.length; index++) {"
|
" for (var index = 0; index < items.length; index++) {"
|
||||||
" tmp = items[index].split(\"=\");"
|
" tmp = items[index].split(\"=\");"
|
||||||
" if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);"
|
" if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);"
|
||||||
" }"
|
" }"
|
||||||
|
|
|
@ -41,15 +41,13 @@ ROUTE_IMPL(RouteTokenValid, path, argp)
|
||||||
RegTokenInfo *info = NULL;
|
RegTokenInfo *info = NULL;
|
||||||
|
|
||||||
char *tokenstr;
|
char *tokenstr;
|
||||||
char *msg;
|
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
||||||
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||||
{
|
{
|
||||||
msg = "This route only accepts GET.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
|
|
@ -36,8 +36,6 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
char *authType = ArrayGet(path, 0);
|
char *authType = ArrayGet(path, 0);
|
||||||
char *sessionId;
|
char *sessionId;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!authType)
|
if (!authType)
|
||||||
{
|
{
|
||||||
/* This should never happen */
|
/* This should never happen */
|
||||||
|
@ -58,10 +56,9 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
config = ConfigLock(args->matrixArgs->db);
|
config = ConfigLock(args->matrixArgs->db);
|
||||||
if (!config)
|
if (!config)
|
||||||
{
|
{
|
||||||
msg = "Internal server error: failed to lock configuration.";
|
|
||||||
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
|
Log(LOG_ERR, "UIA fallback failed to lock configuration.");
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
request = JsonDecode(HttpServerStream(args->context));
|
request = JsonDecode(HttpServerStream(args->context));
|
||||||
|
@ -96,17 +93,15 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
}
|
}
|
||||||
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
else if (HttpRequestMethodGet(args->context) != HTTP_GET)
|
||||||
{
|
{
|
||||||
msg = "Route only supports GET.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
return MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionId = HashMapGet(requestParams, "session");
|
sessionId = HashMapGet(requestParams, "session");
|
||||||
if (!sessionId)
|
if (!sessionId)
|
||||||
{
|
{
|
||||||
msg = "'session' parameter is unset.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
return MatrixErrorCreate(M_MISSING_PARAM, msg);
|
return MatrixErrorCreate(M_MISSING_PARAM, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponseHeader(args->context, "Content-Type", "text/html");
|
HttpResponseHeader(args->context, "Content-Type", "text/html");
|
||||||
|
@ -126,25 +121,25 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
HtmlEndForm(stream);
|
HtmlEndForm(stream);
|
||||||
HtmlBeginJs(stream);
|
HtmlBeginJs(stream);
|
||||||
StreamPrintf(stream,
|
StreamPrintf(stream,
|
||||||
"function buildRequest() {"
|
"function buildRequest() {"
|
||||||
" let user = document.getElementById('user').value;"
|
" let user = document.getElementById('user').value;"
|
||||||
" let pass = document.getElementById('password').value;"
|
" let pass = document.getElementById('password').value;"
|
||||||
" if (!user || !pass) {"
|
" if (!user || !pass) {"
|
||||||
" setFormError('Please specify a username and password.');"
|
" setFormError('Please specify a username and password.');"
|
||||||
" return false;"
|
" return false;"
|
||||||
" }"
|
" }"
|
||||||
" return {"
|
" return {"
|
||||||
" auth: {"
|
" auth: {"
|
||||||
" type: '%s',"
|
" type: '%s',"
|
||||||
" identifier: {"
|
" identifier: {"
|
||||||
" type: 'm.id.user',"
|
" type: 'm.id.user',"
|
||||||
" user: user"
|
" user: user"
|
||||||
" },"
|
" },"
|
||||||
" password: pass,"
|
" password: pass,"
|
||||||
" session: '%s'"
|
" session: '%s'"
|
||||||
" }"
|
" }"
|
||||||
" };"
|
" };"
|
||||||
"}", authType, sessionId);
|
"}", authType, sessionId);
|
||||||
HtmlEndJs(stream);
|
HtmlEndJs(stream);
|
||||||
}
|
}
|
||||||
else if (StrEquals(authType, "m.login.registration_token"))
|
else if (StrEquals(authType, "m.login.registration_token"))
|
||||||
|
@ -191,10 +186,10 @@ ROUTE_IMPL(RouteUiaFallback, path, argp)
|
||||||
"function processResponse(xhr) {"
|
"function processResponse(xhr) {"
|
||||||
" let r = JSON.parse(xhr.responseText);"
|
" let r = JSON.parse(xhr.responseText);"
|
||||||
" console.log(r);"
|
" console.log(r);"
|
||||||
" if (xhr.status == 200 || r.completed.includes('%s')) {"
|
" if (xhr.status == 200 || r.completed.includes('%s')) {"
|
||||||
" if (window.onAuthDone) {"
|
" if (window.onAuthDone) {"
|
||||||
" window.onAuthDone();"
|
" window.onAuthDone();"
|
||||||
" } else if (window.opener && window.opener.postMessage) {"
|
" } else if (window.opener && window.opener.postMessage) {"
|
||||||
" window.opener.postMessage('authDone', '*');"
|
" window.opener.postMessage('authDone', '*');"
|
||||||
" } else {"
|
" } else {"
|
||||||
" setFormError('Client error.');"
|
" setFormError('Client error.');"
|
||||||
|
|
|
@ -48,8 +48,6 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
char *token = NULL;
|
char *token = NULL;
|
||||||
char *value = NULL;
|
char *value = NULL;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
Config *config = ConfigLock(db);
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
|
@ -65,18 +63,15 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
userId = UserIdParse(username, serverName);
|
userId = UserIdParse(username, serverName);
|
||||||
if (!userId)
|
if (!userId)
|
||||||
{
|
{
|
||||||
msg = "Invalid user ID.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_INVALID_PARAM, msg);
|
response = MatrixErrorCreate(M_INVALID_PARAM, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
if (strcmp(userId->server, serverName))
|
if (strcmp(userId->server, serverName))
|
||||||
{
|
{
|
||||||
/* TODO: Implement lookup over federation. */
|
/* TODO: Implement lookup over federation. */
|
||||||
msg = "User profile endpoint currently doesn't support lookup over "
|
|
||||||
"federation.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +82,8 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
user = UserLock(db, userId->localpart);
|
user = UserLock(db, userId->localpart);
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
msg = "Couldn't lock user.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
HttpResponseStatus(args->context, HTTP_NOT_FOUND);
|
||||||
response = MatrixErrorCreate(M_NOT_FOUND, msg);
|
response = MatrixErrorCreate(M_NOT_FOUND, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +138,7 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
StrEquals(entry, "avatar_url"))
|
StrEquals(entry, "avatar_url"))
|
||||||
{
|
{
|
||||||
/* Check if user has privilege to do that action. */
|
/* Check if user has privilege to do that action. */
|
||||||
if (StrEquals(userId->localpart, UserGetName(user)))
|
if (strcmp(userId->localpart, UserGetName(user)) == 0)
|
||||||
{
|
{
|
||||||
value = JsonValueAsString(HashMapGet(request, entry));
|
value = JsonValueAsString(HashMapGet(request, entry));
|
||||||
/* TODO: Make UserSetProfile notify other
|
/* TODO: Make UserSetProfile notify other
|
||||||
|
@ -154,16 +148,14 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
/* User is not allowed to carry-on the action */
|
/* User is not allowed to carry-on the action */
|
||||||
msg = "Cannot change another user's profile.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
HttpResponseStatus(args->context, HTTP_FORBIDDEN);
|
||||||
response = MatrixErrorCreate(M_FORBIDDEN, msg);
|
response = MatrixErrorCreate(M_FORBIDDEN, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
msg = "Invalid property being changed.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNRECOGNIZED, msg);
|
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,9 +166,8 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
msg = "Route only accepts GET and PUT.";
|
|
||||||
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
|
||||||
response = MatrixErrorCreate(M_UNKNOWN, msg);
|
response = MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
finish:
|
finish:
|
||||||
|
|
|
@ -35,12 +35,7 @@ ROUTE_IMPL(RouteVersions, path, argp)
|
||||||
(void) path;
|
(void) path;
|
||||||
(void) argp;
|
(void) argp;
|
||||||
|
|
||||||
#define DECLARE_SPEC_VERSION(x) ArrayAdd(versions, JsonValueString(x))
|
ArrayAdd(versions, JsonValueString("v1.6"));
|
||||||
|
|
||||||
DECLARE_SPEC_VERSION("v1.7");
|
|
||||||
/* Declare additional spec version support here. */
|
|
||||||
|
|
||||||
#undef DECLARE_SPEC_VERSION
|
|
||||||
|
|
||||||
HashMapSet(response, "versions", JsonValueArray(versions));
|
HashMapSet(response, "versions", JsonValueArray(versions));
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -37,14 +37,11 @@ ROUTE_IMPL(RouteWellKnown, path, argp)
|
||||||
|
|
||||||
Config *config = ConfigLock(args->matrixArgs->db);
|
Config *config = ConfigLock(args->matrixArgs->db);
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
{
|
{
|
||||||
Log(LOG_ERR, "Well-known endpoint failed to lock configuration.");
|
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);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrEquals(ArrayGet(path, 0), "client"))
|
if (StrEquals(ArrayGet(path, 0), "client"))
|
||||||
|
|
|
@ -42,16 +42,14 @@ ROUTE_IMPL(RouteWhoami, path, argp)
|
||||||
char *token;
|
char *token;
|
||||||
char *userID;
|
char *userID;
|
||||||
char *deviceID;
|
char *deviceID;
|
||||||
char *msg;
|
|
||||||
|
|
||||||
Config *config = ConfigLock(db);
|
Config *config = ConfigLock(db);
|
||||||
|
|
||||||
if (!config)
|
if (!config)
|
||||||
{
|
{
|
||||||
msg = "Internal server error: couldn't lock database.";
|
|
||||||
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
|
Log(LOG_ERR, "Who am I endpoint failed to lock configuration.");
|
||||||
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
|
||||||
return MatrixErrorCreate(M_UNKNOWN, msg);
|
return MatrixErrorCreate(M_UNKNOWN, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) path;
|
(void) path;
|
||||||
|
|
|
@ -33,14 +33,12 @@
|
||||||
static HashMap *
|
static HashMap *
|
||||||
StateResolveV1(Array * states)
|
StateResolveV1(Array * states)
|
||||||
{
|
{
|
||||||
(void) states;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static HashMap *
|
static HashMap *
|
||||||
StateResolveV2(Array * states)
|
StateResolveV2(Array * states)
|
||||||
{
|
{
|
||||||
(void) states;
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/Uia.c
11
src/Uia.c
|
@ -222,8 +222,6 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
HashMap *dbJson;
|
HashMap *dbJson;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
char *msg;
|
|
||||||
|
|
||||||
if (!flows)
|
if (!flows)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -244,9 +242,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (JsonValueType(val) != JSON_OBJECT)
|
if (JsonValueType(val) != JSON_OBJECT)
|
||||||
{
|
{
|
||||||
msg = "'auth' is not an object.";
|
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,9 +252,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "'auth->session' is unset or not a string.";
|
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,9 +311,8 @@ UiaComplete(Array * flows, HttpServerContext * context, Db * db,
|
||||||
|
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
{
|
{
|
||||||
msg = "'auth->type' is unset or not a string.";
|
|
||||||
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
HttpResponseStatus(context, HTTP_BAD_REQUEST);
|
||||||
*response = MatrixErrorCreate(M_BAD_JSON, msg);
|
*response = MatrixErrorCreate(M_BAD_JSON, NULL);
|
||||||
ret = 0;
|
ret = 0;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
29
src/User.c
29
src/User.c
|
@ -749,7 +749,7 @@ UserGetPrivileges(User * user)
|
||||||
return USER_NONE;
|
return USER_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserDecodePrivileges(JsonValueAsArray(HashMapGet(DbJson(user->ref), "privileges")));
|
return UserDecodePrivileges(HashMapGet(DbJson(user->ref), "privileges"));
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -768,7 +768,7 @@ UserSetPrivileges(User * user, int privileges)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = JsonValueArray(UserEncodePrivileges(privileges));
|
val = UserEncodePrivileges(privileges);
|
||||||
if (!val)
|
if (!val)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -779,26 +779,31 @@ UserSetPrivileges(User * user, int privileges)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
UserDecodePrivileges(Array * arr)
|
UserDecodePrivileges(JsonValue * val)
|
||||||
{
|
{
|
||||||
int privileges = USER_NONE;
|
int privileges = USER_NONE;
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
|
Array *arr;
|
||||||
|
|
||||||
if (!arr)
|
if (!val)
|
||||||
{
|
{
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < ArraySize(arr); i++)
|
if (JsonValueType(val) == JSON_ARRAY)
|
||||||
{
|
{
|
||||||
JsonValue *val = ArrayGet(arr, i);
|
arr = JsonValueAsArray(val);
|
||||||
if (!val || JsonValueType(val) != JSON_STRING)
|
for (i = 0; i < ArraySize(arr); i++)
|
||||||
{
|
{
|
||||||
continue;
|
val = ArrayGet(arr, i);
|
||||||
}
|
if (!val || JsonValueType(val) != JSON_STRING)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
privileges |= UserDecodePrivilege(JsonValueAsString(val));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
@ -846,7 +851,7 @@ UserDecodePrivilege(const char *p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Array *
|
JsonValue *
|
||||||
UserEncodePrivileges(int privileges)
|
UserEncodePrivileges(int privileges)
|
||||||
{
|
{
|
||||||
Array *arr = ArrayCreate();
|
Array *arr = ArrayCreate();
|
||||||
|
@ -878,7 +883,7 @@ UserEncodePrivileges(int privileges)
|
||||||
#undef A
|
#undef A
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
return arr;
|
return JsonValueArray(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserId *
|
UserId *
|
||||||
|
|
|
@ -42,7 +42,54 @@
|
||||||
#include <Cytoplasm/Db.h>
|
#include <Cytoplasm/Db.h>
|
||||||
#include <Cytoplasm/Int64.h>
|
#include <Cytoplasm/Int64.h>
|
||||||
|
|
||||||
#include <Schema/RegToken.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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ``Use'' the specified registration token by increasing the used
|
* ``Use'' the specified registration token by increasing the used
|
||||||
|
@ -85,6 +132,7 @@ RegTokenCreate(Db *, char *, char *, UInt64, Int64, int);
|
||||||
* .Fn RegTokenClose .
|
* .Fn RegTokenClose .
|
||||||
*/
|
*/
|
||||||
extern void RegTokenFree(RegTokenInfo *);
|
extern void RegTokenFree(RegTokenInfo *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a boolean value indicating whether or not the specified token
|
* Return a boolean value indicating whether or not the specified token
|
||||||
* is valid. A registration token is only valid if it has not expired
|
* is valid. A registration token is only valid if it has not expired
|
||||||
|
|
|
@ -105,8 +105,6 @@ ROUTE(RouteRoomAliases);
|
||||||
|
|
||||||
ROUTE(RouteAdminDeactivate);
|
ROUTE(RouteAdminDeactivate);
|
||||||
|
|
||||||
ROUTE(RouteAdminTokens);
|
|
||||||
|
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -286,13 +286,13 @@ extern int UserSetPrivileges(User *, int);
|
||||||
* Decode the JSON that represents the user privileges into a packed
|
* Decode the JSON that represents the user privileges into a packed
|
||||||
* bit field for simple manipulation.
|
* bit field for simple manipulation.
|
||||||
*/
|
*/
|
||||||
extern int UserDecodePrivileges(Array *);
|
extern int UserDecodePrivileges(JsonValue *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the packed bit field that represents user privileges as a
|
* Encode the packed bit field that represents user privileges as a
|
||||||
* JSON value.
|
* JSON value.
|
||||||
*/
|
*/
|
||||||
extern Array *UserEncodePrivileges(int);
|
extern JsonValue *UserEncodePrivileges(int);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a string privilege into its bit in the bit field. This is
|
* Convert a string privilege into its bit in the bit field. This is
|
||||||
|
|
Loading…
Reference in a new issue