Redoing program to center around JSONRPC instead of a more REST-like API
This commit is contained in:
parent
ed4bbeb608
commit
be8936a665
15 changed files with 209 additions and 170 deletions
10
args/args.go
10
args/args.go
|
@ -22,12 +22,12 @@ func Parse() {
|
||||||
/* module-specific variable to avoid re-parsing flags */
|
/* module-specific variable to avoid re-parsing flags */
|
||||||
var flagsParsed bool = false;
|
var flagsParsed bool = false;
|
||||||
|
|
||||||
/* what JSON file to read config values from */
|
/* what file to read the configuration */
|
||||||
var confLocation = flag.String("conf", "./config.txt", "Config file to read from")
|
var authJson = flag.String("auth", "./auth.json", "Authorization file to read from")
|
||||||
/* @return set boolean will be true if argument is not nil */
|
/* @return set boolean will be true if argument is not nil */
|
||||||
func GetConfLocation() (location string, set bool) {
|
func GetAuthJson() (location string, set bool) {
|
||||||
if confLocation == nil {return "", false}
|
if authJson == nil {return "", false}
|
||||||
return *confLocation, true;
|
return *authJson, true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TCP port to bind to */
|
/* TCP port to bind to */
|
||||||
|
|
9
auth-sample.json
Normal file
9
auth-sample.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"WGV99fSwgKhdQSa89HQIGxas": [
|
||||||
|
{"method":"send","params":{"recipient":["+16028675309"]}},
|
||||||
|
{"method":"send","params":{"groupID":["67a13c3e-8d29-2539-ce8e-41129c349d6d"]}}
|
||||||
|
],
|
||||||
|
"ZQR3T6lqsvnXcgcWhpPOWWdv": [
|
||||||
|
{"method":"receive","params":{"envelope":{"source":"67a13c3e-8d29-2539-ce8e-41129c349d6d"}}}
|
||||||
|
]
|
||||||
|
}
|
37
auth/auth.go
Normal file
37
auth/auth.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
/* This file contains the AuthAuthConfig object and its methods, which handle reading
|
||||||
|
from a config file and matching requests to the whitelist. */
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Stores a map between a string (bearer token) and a list of unmarshaled JSONS */
|
||||||
|
var authConfig any;
|
||||||
|
var authConfigSetup bool = false;
|
||||||
|
|
||||||
|
/* Opens and reads a file at the path */
|
||||||
|
func SetupAuthConfig(filePath string) (err error) {
|
||||||
|
if authConfigSetup {return errors.New("Auth configuration already set up!")}
|
||||||
|
|
||||||
|
// Open and read file contents
|
||||||
|
fileContents, err := os.ReadFile(filePath);
|
||||||
|
if err != nil {return}
|
||||||
|
|
||||||
|
// Unmarshal
|
||||||
|
authConfig = unmarshalJSON(fileContents);
|
||||||
|
if authConfig == nil {return errors.New("Invalid JSON config!");}
|
||||||
|
|
||||||
|
print(match(authConfig, authConfig), "\n")
|
||||||
|
|
||||||
|
// Finish setup
|
||||||
|
authConfigSetup = true;
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets a reference copy to the config data */
|
||||||
|
func GetAuthConfigData() (any, bool) {
|
||||||
|
return authConfig, authConfigSetup;
|
||||||
|
}
|
60
auth/json.go
Normal file
60
auth/json.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
/* This file contains some JSON helper functions */
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Unmarshals a JSON into a recursive map. Returns nil on error */
|
||||||
|
func unmarshalJSON(marshaledJSON []byte) (unmarshaled any) {
|
||||||
|
json.Unmarshal(marshaledJSON, &unmarshaled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Meat and bones of determining if a request is allowed by a filter */
|
||||||
|
func match(request any, filter any) bool {
|
||||||
|
// Check that the types are the same
|
||||||
|
if reflect.TypeOf(request) != reflect.TypeOf(filter) {return false}
|
||||||
|
|
||||||
|
// Can safely switch on type of one object at this point since they're equal
|
||||||
|
switch filter.(type) {
|
||||||
|
|
||||||
|
// Key-value pairs
|
||||||
|
case map[string]any:
|
||||||
|
// Check for every key that's in the filter
|
||||||
|
for key := range filter.(map[string]any) {
|
||||||
|
// that it's in the request
|
||||||
|
if _, ok := request.(map[string]any)[key]; !ok {return false}
|
||||||
|
|
||||||
|
// And recursively check that the value is equal
|
||||||
|
if !match(request.(map[string]any)[key], filter.(map[string]any)[key]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* Arrays attempt to match every item in the filter to ANY item in the
|
||||||
|
request. Duplicates in the filter are treated as one */
|
||||||
|
case []any:
|
||||||
|
// Check that for every item in the filter
|
||||||
|
for i := 0; i < len(filter.([]any)); i ++ {
|
||||||
|
foundMatch := false;
|
||||||
|
// That something matches in the request
|
||||||
|
for j := 0; j < len(request.([]any)); j ++ {
|
||||||
|
if match(filter.([]any)[i], request.([]any)[j]) {
|
||||||
|
foundMatch = true;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cannot find a match for something in the filter
|
||||||
|
if !foundMatch {return false}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Otherwise compare the objects directly using reflect
|
||||||
|
default: return reflect.DeepEqual(request, filter)
|
||||||
|
}
|
||||||
|
}
|
35
auth/readme.md
Normal file
35
auth/readme.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Auth - Signal-CLI HTTP
|
||||||
|
|
||||||
|
This module handles the reading and parsing of the auth JSON file. It also acts as a verifier in relation to that information. The file is a JSON object. It acts as a whitelist for which bearer token can do what action. It is passed to the HTTP endpoint via the `Authorization: Bearer <bearerToken>` header.
|
||||||
|
|
||||||
|
Here's a sample auth JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"WGV99fSwgKhdQSa89HQIGxas": [
|
||||||
|
{"method":"send","params":{"recipient":["+16028675309"]}},
|
||||||
|
{"method":"send","params":{"groupID":["67a13c3e-8d29-2539-ce8e-41129c349d6d"]}},
|
||||||
|
],
|
||||||
|
"ZQR3T6lqsvnXcgcWhpPOWWdv": [
|
||||||
|
{"method":"receive","params":{"envelope":{"source":"67a13c3e-8d29-2539-ce8e-41129c349d6d"}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When an HTTP request comes in, this software will do the following:
|
||||||
|
|
||||||
|
1. Check that there's an `Authorization` header
|
||||||
|
2. Get the authorization header's value (bearer token)
|
||||||
|
3. Read the JSON array corresponding to the bearer token.
|
||||||
|
4. See if any JSON object in that array (called a filter) does not have any data the request JSON doesn't.
|
||||||
|
5. If the statement in step 4 is true, forward the request into the signal-cli process and return the response.
|
||||||
|
|
||||||
|
So for example, the reqest `{"method":"send","params":{"recipient":["+16028675309"],"message":"message"},"id":"SomeID"},` would be allowed by the filter `{"method":"send","params":{"recipient":["+16028675309"]}}` because the filter does not have any data the request does not. But `{"method":"send","params":{"recipient":["+5555555555"],"message":"message"},"id":"SomeID"},` would not because the phone number differs.
|
||||||
|
|
||||||
|
These filters can be as granular as you want.
|
||||||
|
|
||||||
|
Here's what each filter JSON object in the above sample JSON does:
|
||||||
|
|
||||||
|
`{"method":"send","params":{"recipient":["+16028675309"]}}` allows sending to `+16028675309` (any message, timestamp, etc.)
|
||||||
|
`{"method":"send","params":{"groupID":["67a13c3e-8d29-2539-ce8e-41129c349d6d"]}}`: allows sending to group `67a13c3e-8d29-2539-ce8e-41129c349d6d` (any message, timestamp, etc.)
|
||||||
|
`{"method":"receive","params":{"envelope":{"source":"67a13c3e-8d29-2539-ce8e-41129c349d6d"}}}` allows receiving from group `67a13c3e-8d29-2539-ce8e-41129c349d6d`
|
61
conf/conf.go
61
conf/conf.go
|
@ -1,61 +0,0 @@
|
||||||
package conf
|
|
||||||
|
|
||||||
/* This file contains the Config object and its methods, which handle reading
|
|
||||||
from a config file and matching requests to the whitelist. */
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Object to handle what is in a JSON config */
|
|
||||||
type Config struct {
|
|
||||||
configData map[string][]string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Default Config object */
|
|
||||||
var GlobalConfig * Config;
|
|
||||||
|
|
||||||
/* Opens and reads a file at the path */
|
|
||||||
func NewConfig(filePath string) (newConfig *Config, err error) {
|
|
||||||
// Open file
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {return}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// Create configuration
|
|
||||||
newConfigData := make(map[string][]string);
|
|
||||||
|
|
||||||
// Read lines into newConfigData
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
parts := strings.SplitN(scanner.Text(), " ", 2);
|
|
||||||
if len(parts) != 2 {err = errors.New("Bad config file!"); return;}
|
|
||||||
newConfigData[parts[0]] = append(newConfigData[parts[0]], parts[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Config object and copy a reference to newConfigData into it
|
|
||||||
return &Config{configData: newConfigData}, nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Gets a reference copy to the config data */
|
|
||||||
func (config * Config) GetConfigData() map[string][]string {
|
|
||||||
return config.configData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns if a bearer key is authorized for the path in this Config object
|
|
||||||
@return false for any situation that isn't a valid match */
|
|
||||||
func (config * Config) ValidateBearerKey(bearerKey string, request string) bool {
|
|
||||||
paths, exists := config.configData[bearerKey];
|
|
||||||
if !exists {return false}
|
|
||||||
|
|
||||||
for _, matchTo := range paths {
|
|
||||||
if match(request, matchTo) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Conf - Signal-CLI HTTP
|
|
||||||
|
|
||||||
This module handles reading and parsing the config file, and acting as a verifier for the `Authorization` header on the HTTP requests.
|
|
||||||
|
|
||||||
The config file is made up of multiple lines. The first token in each line is the `Authorization` bearer token. This cannot have spaces but can be any string. Choose wisely. The remainder of the line contains a path that the `Authorization` header is checked against. It does not matter if you include a leading or trailing slash.
|
|
||||||
|
|
||||||
Here's a sample config:
|
|
||||||
|
|
||||||
```
|
|
||||||
WGV99fSwgKhdQSa89HQIGxas /+16028675309/room/roomID/*
|
|
||||||
WGV99fSwgKhdQSa89HQIGxas /+16028675309/direct/username.69/send
|
|
||||||
ZQR3T6lqsvnXcgcWhpPOWWdv +16028675309/direct/username.69/send/
|
|
||||||
```
|
|
||||||
|
|
||||||
The config file is a **whitelist** for each bearer token to access a specific endpoint (or set of endpoints). The endpoints for this program are granular enough to only allow one action for each endpoint, so this level of whitelisting should™ be okay.
|
|
||||||
|
|
||||||
There is a regex-like behavior to these paths using the `*` and `?` characters. For the regex-like behavior to be triggered these characters must be by themselves per path segment (no other characters not separated by a `/` or a start or end of string).
|
|
||||||
|
|
||||||
The `*` character matches to any number of path segments. The `?` character matches to only one segment. Here's some examples:
|
|
||||||
|
|
||||||
* `HZJWwB0TAjz6pjAHosII5ofR /+16028675309/*` will allow the bearer token to access any endpoint with the phone number `+16028675309`.
|
|
||||||
* `HZJWwB0TAjz6pjAHosII5ofR /+16028675309/direct/?/send` will allow the bearer token to send a direct message to anyone on that phone number.
|
|
|
@ -1,54 +0,0 @@
|
||||||
package conf
|
|
||||||
|
|
||||||
/* This file contains regex helper functions for parsing configs */
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Splits and normalises */
|
|
||||||
func splitPath(path string) []string {
|
|
||||||
return strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attempts to match a request path to a set of whitelisted paths
|
|
||||||
@return false for anything other than a valid match */
|
|
||||||
func match(request string, matchTo string) bool {
|
|
||||||
return matchSegments(splitPath(request), splitPath(matchTo));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns false for anything other than a valid match */
|
|
||||||
func matchSegments(request []string, matchTo []string) bool {
|
|
||||||
/* This is a recursive function which, at each recursion level, matches the
|
|
||||||
path segments that are in the front of the request and matchTo lists
|
|
||||||
It matches identical strings, anything to &, and splits in two when
|
|
||||||
matching anything to *, to account for consuming or not consuming the *
|
|
||||||
at the current recursion level. */
|
|
||||||
|
|
||||||
// Recursion base case for perfect match
|
|
||||||
if len(request) == 0 && len(matchTo) == 0 {return true}
|
|
||||||
// End of path for one but not the other
|
|
||||||
if (len(request) & len(matchTo)) == 0 {return false}
|
|
||||||
|
|
||||||
// Grab current path segments
|
|
||||||
requestCurrent := request[0];
|
|
||||||
matchToCurrent := matchTo[0];
|
|
||||||
|
|
||||||
// & character and direct match have the same behavior
|
|
||||||
if (matchToCurrent == "&") || (requestCurrent == matchToCurrent) {
|
|
||||||
return matchSegments(request[1:], matchTo[1:]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// * character
|
|
||||||
if (matchToCurrent == "*") {
|
|
||||||
// These are split for performance
|
|
||||||
// Usually the * only refers to 1 or 2 things so putting consumption
|
|
||||||
// first is probably a better choice
|
|
||||||
if (matchSegments(request[1:], matchTo[1:])) {return true}
|
|
||||||
if (matchSegments(request[1:], matchTo)) {return true}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code will reach here if there's no match for the current segment
|
|
||||||
return false;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
WGV99fSwgKhdQSa89HQIGxas /+16028675309/room/roomID/*
|
|
||||||
WGV99fSwgKhdQSa89HQIGxas /+16028675309/direct/username.69/send
|
|
||||||
ZQR3T6lqsvnXcgcWhpPOWWdv +16028675309/direct/username.69/send/
|
|
28
main.go
28
main.go
|
@ -4,24 +4,36 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"signal-cli-http/args"
|
"signal-cli-http/args"
|
||||||
"signal-cli-http/conf"
|
"signal-cli-http/auth"
|
||||||
"signal-cli-http/http"
|
"signal-cli-http/web"
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
var wg sync.WaitGroup; wg.Add(1);
|
||||||
|
|
||||||
// Read arguments
|
// Read arguments
|
||||||
args.Parse();
|
args.Parse();
|
||||||
configLocation, confLocationSet := args.GetConfLocation();
|
configLocation, confLocationSet := args.GetAuthJson();
|
||||||
if !confLocationSet {log.Default().Print("No config value!"); return}
|
if !confLocationSet {log.Default().Print("No auth config value!"); return;}
|
||||||
log.Default().Print("Reading config value from ", configLocation);
|
log.Default().Print("Reading auth config value from ", configLocation);
|
||||||
|
|
||||||
// Set up config
|
// Set up config
|
||||||
conf.GlobalConfig, _ = conf.NewConfig(configLocation);
|
err := auth.SetupAuthConfig(configLocation);
|
||||||
if conf.GlobalConfig == nil {log.Default().Print("Error reading config"); return}
|
if err != nil {log.Default().Print("Error reading config: ", err); return;}
|
||||||
|
log.Default().Print(auth.GetAuthConfigData());
|
||||||
|
|
||||||
port, portSet := args.GetHTTPPort();
|
port, portSet := args.GetHTTPPort();
|
||||||
if !portSet {log.Default().Print("No port value!"); return;}
|
if !portSet {log.Default().Print("No port value!"); return;}
|
||||||
http.StartWebserver(port)
|
log.Default().Print("Listening on port ", port);
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done();
|
||||||
|
web.StartWebserver(port);
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Default().Print("Startup tasks complete!");
|
||||||
|
wg.Wait();
|
||||||
}
|
}
|
15
readme.md
15
readme.md
|
@ -2,14 +2,13 @@
|
||||||
|
|
||||||
**Very** early in development.
|
**Very** early in development.
|
||||||
|
|
||||||
Very simple HTTP frontend to [signal-cli](https://github.com/AsamK/signal-cli).
|
Very simple HTTP frontend to [signal-cli](https://github.com/AsamK/signal-cli) JSON RPC.
|
||||||
|
|
||||||
Please also read the following README files for the individual modules:
|
Please see the JSONRPC documentation for `signal-cli`: [https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc)
|
||||||
|
|
||||||
* [args](args/readme.md) - handles command line arguments.
|
Please also read the following README files for the individual modules to understand how to configure and interact with this program:
|
||||||
* [conf](conf/readme.md) - handles the config file.
|
|
||||||
|
|
||||||
Too be implemented:
|
* [args](args/readme.md) handles command line arguments.
|
||||||
|
* [auth](auth/readme.md) handles the authentication JSON and checking requests.
|
||||||
* subprocess - handles running the binaries which communicate with the daemon.
|
* [subprocess](subprocess/readme.md) manages the underlying `signal-cli` JSONRPC process, along with caching incoming messages.
|
||||||
* web - handles the incoming http requests.
|
* [web](web/readme.md) - handles the HTTP requests to this program, including the necessary edge cases.
|
5
subprocess/readme.md
Normal file
5
subprocess/readme.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Subprocess - Signal-CLI HTTP
|
||||||
|
|
||||||
|
This module spawns and handles IO for the signal-cli process.
|
||||||
|
|
||||||
|
Do not pass an object with the "id" key into this module's methods. It will reject the request for that reason.
|
|
@ -1,6 +1,6 @@
|
||||||
package subprocess
|
package subprocess
|
||||||
|
|
||||||
/* This file manages creating the command line arguments to the subprocess */
|
/* This file manages verifying/sanatizing the request and response and forwarding it to the subprocess */
|
||||||
|
|
||||||
/* Method which module http calls to create the subprocess */
|
/* Method which module http calls to create the subprocess */
|
||||||
func Run(path string, body []byte) (status int, bodyContents []byte, err error) {
|
func Run(path string, body []byte) (status int, bodyContents []byte, err error) {
|
|
@ -1,9 +1,9 @@
|
||||||
package http
|
package web
|
||||||
|
|
||||||
/* This file handles listening to HTTP requests */
|
/* This file handles listening to HTTP requests */
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"signal-cli-http/conf"
|
//"signal-cli-http/auth"
|
||||||
"signal-cli-http/subprocess"
|
"signal-cli-http/subprocess"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -30,14 +30,13 @@ func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
bearer := authArr[0];
|
bearer := authArr[0];
|
||||||
|
|
||||||
// Check that the request is allowed for the path
|
// Check that the request is allowed for the path
|
||||||
if !conf.GlobalConfig.ValidateBearerKey(bearer, r.URL.Path) {
|
/*if !conf.GlobalConfig.ValidateBearerKey(bearer, r.URL.Path) {
|
||||||
w.WriteHeader(403);
|
w.WriteHeader(403);
|
||||||
w.Write([]byte("Bearer key not whitelisted for this path\n"))
|
w.Write([]byte("Bearer key not whitelisted for this path\n"))
|
||||||
return;
|
return;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// OK authentication wise
|
|
||||||
|
|
||||||
|
// Read request body
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(500);
|
w.WriteHeader(500);
|
||||||
|
@ -56,8 +55,14 @@ func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond to client with status
|
// Respond to client with status
|
||||||
w.WriteHeader(status);
|
if status == 0 {
|
||||||
w.Write(bodyContent);
|
w.WriteHeader(200);
|
||||||
|
w.Write(bodyContent);
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(400);
|
||||||
|
w.Write([]byte("Program exited with status " + fmt.Sprint(status)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Log the request
|
// Log the request
|
||||||
log.Default().Print("HTTP Request: ", bearer, " " , r.URL.Path, " ", status)
|
log.Default().Print("HTTP Request: ", bearer, " " , r.URL.Path, " ", status)
|
17
web/readme.md
Normal file
17
web/readme.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Web - Signal-CLI HTTP
|
||||||
|
|
||||||
|
This module handles the HTTP requests. The path used for requests genuinely does not matter. However, every request must have a JSON object body, and an `Authorization: bearer <token>` header. The response will also always be a JSON.
|
||||||
|
|
||||||
|
Possible response codes:
|
||||||
|
|
||||||
|
* 400 Bad Request: Bad JSON, missing Authorization header, etc.
|
||||||
|
* 401 Unauthorized: bearer token not allowed for presented request.
|
||||||
|
* 500 Internal Server Error: Self explanitory.
|
||||||
|
* 501 Not Implemented: Will be returned for message receiving.
|
||||||
|
* 200 OK: Request was forwarded to JSONRPC subprocess and that process returned something, including if the "error" key is present in the returned JSON.
|
||||||
|
|
||||||
|
Any non-200 response code will come with a JSON with the following format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"error":"Error message string"}
|
||||||
|
```
|
Loading…
Add table
Add a link
Reference in a new issue