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