Add KeyToken authorization
This commit is contained in:
parent
16f6ab4861
commit
b1bd278f9b
@ -10,12 +10,17 @@ HASH=$(shell git rev-parse HEAD)
|
||||
build: swagger fmt
|
||||
mkdir -p _build
|
||||
rm -f ./_build/scn_backend
|
||||
go generate ./...
|
||||
CGO_ENABLED=1 go build -v -o _build/scn_backend -tags "timetzdata sqlite_fts5 sqlite_foreign_keys" ./cmd/scnserver
|
||||
|
||||
run: build
|
||||
mkdir -p .run-data
|
||||
_build/scn_backend
|
||||
|
||||
gow:
|
||||
# go install github.com/mitranim/gow@latest
|
||||
gow run blackforestbytes.com/portfoliomanager2/cmd/server
|
||||
|
||||
docker: build
|
||||
[ ! -f "DOCKER_GIT_INFO" ] || rm DOCKER_GIT_INFO
|
||||
git rev-parse --abbrev-ref HEAD >> DOCKER_GIT_INFO
|
||||
|
@ -49,11 +49,27 @@
|
||||
|
||||
- ios purchase verification
|
||||
|
||||
- re-add ack labels as compat table for v1 api user
|
||||
- [X] re-add ack labels as compat table for v1 api user
|
||||
- return channel as "[..] asdf" in compat methods (mark clients as compat and send compat FB to them...)
|
||||
(then we can replace the old server without switching phone clients)
|
||||
(still needs switching of the send-script)
|
||||
-
|
||||
|
||||
- do not use uuidgen in bash script (potetnially not installed) - use `head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 `
|
||||
|
||||
- move to KeyToken model
|
||||
* [X] User can have multiple keys with different permissions
|
||||
* [X] compat simply uses default-keys
|
||||
* [X] CRUD routes for keys
|
||||
* [X] KeyToken.messagecounter
|
||||
* [ ] update old-data migration to create keys
|
||||
* [ ] unit tests
|
||||
|
||||
- We no longer have a route to reshuffle all keys (previously in updateUser), add a /user/:uid/keys/reset ?
|
||||
Would delete all existing keys and create 3 new ones?
|
||||
|
||||
- the explanation of user_id and key in ./website is now wrong (was already wrong and is even wronger now that there are multiple KeyToken's with permissions etc)
|
||||
|
||||
- swagger broken?
|
||||
|
||||
#### PERSONAL
|
||||
|
||||
|
295
scnserver/_gen/enum-generate.go
Normal file
295
scnserver/_gen/enum-generate.go
Normal file
@ -0,0 +1,295 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/rext"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EnumDefVal struct {
|
||||
VarName string
|
||||
Value string
|
||||
Description *string
|
||||
}
|
||||
|
||||
type EnumDef struct {
|
||||
File string
|
||||
EnumTypeName string
|
||||
Type string
|
||||
Values []EnumDefVal
|
||||
}
|
||||
|
||||
var rexPackage = rext.W(regexp.MustCompile("^package\\s+(?P<name>[A-Za-z0-9_]+)\\s*$"))
|
||||
|
||||
var rexEnumDef = rext.W(regexp.MustCompile("^\\s*type\\s+(?P<name>[A-Za-z0-9_]+)\\s+(?P<type>[A-Za-z0-9_]+)\\s*//\\s*(@enum:type).*$"))
|
||||
|
||||
var rexValueDef = rext.W(regexp.MustCompile("^\\s*(?P<name>[A-Za-z0-9_]+)\\s+(?P<type>[A-Za-z0-9_]+)\\s*=\\s*(?P<value>(\"[A-Za-z0-9_]+\"|[0-9]+))\\s*(//(?P<descr>.*))?.*$"))
|
||||
|
||||
func main() {
|
||||
dest := os.Args[2]
|
||||
|
||||
wd, err := os.Getwd()
|
||||
errpanic(err)
|
||||
|
||||
files, err := os.ReadDir(wd)
|
||||
errpanic(err)
|
||||
|
||||
allEnums := make([]EnumDef, 0)
|
||||
|
||||
pkgname := ""
|
||||
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("========= %s =========\n\n", f.Name())
|
||||
fileEnums, pn := processFile(f.Name())
|
||||
fmt.Printf("\n")
|
||||
|
||||
allEnums = append(allEnums, fileEnums...)
|
||||
|
||||
if pn != "" {
|
||||
pkgname = pn
|
||||
}
|
||||
}
|
||||
|
||||
if pkgname == "" {
|
||||
panic("no package name found in any file")
|
||||
}
|
||||
|
||||
errpanic(os.WriteFile(dest, []byte(fmtOutput(allEnums, pkgname)), 0o755))
|
||||
}
|
||||
|
||||
func errpanic(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func processFile(fn string) ([]EnumDef, string) {
|
||||
file, err := os.Open(fn)
|
||||
errpanic(err)
|
||||
defer func() { errpanic(file.Close()) }()
|
||||
|
||||
bin, err := io.ReadAll(file)
|
||||
errpanic(err)
|
||||
|
||||
lines := strings.Split(string(bin), "\n")
|
||||
|
||||
enums := make([]EnumDef, 0)
|
||||
|
||||
pkgname := ""
|
||||
|
||||
for i, line := range lines {
|
||||
if i == 0 && strings.HasPrefix(line, "// Code generated by") {
|
||||
break
|
||||
}
|
||||
|
||||
if match, ok := rexPackage.MatchFirst(line); i == 0 && ok {
|
||||
pkgname = match.GroupByName("name").Value()
|
||||
continue
|
||||
}
|
||||
|
||||
if match, ok := rexEnumDef.MatchFirst(line); ok {
|
||||
def := EnumDef{
|
||||
File: fn,
|
||||
EnumTypeName: match.GroupByName("name").Value(),
|
||||
Type: match.GroupByName("type").Value(),
|
||||
Values: make([]EnumDefVal, 0),
|
||||
}
|
||||
enums = append(enums, def)
|
||||
fmt.Printf("Found enum definition { '%s' -> '%s' }\n", def.EnumTypeName, def.Type)
|
||||
}
|
||||
|
||||
if match, ok := rexValueDef.MatchFirst(line); ok {
|
||||
typename := match.GroupByName("type").Value()
|
||||
def := EnumDefVal{
|
||||
VarName: match.GroupByName("name").Value(),
|
||||
Value: match.GroupByName("value").Value(),
|
||||
Description: match.GroupByNameOrEmpty("descr").ValueOrNil(),
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, v := range enums {
|
||||
if v.EnumTypeName == typename {
|
||||
enums[i].Values = append(enums[i].Values, def)
|
||||
found = true
|
||||
if def.Description != nil {
|
||||
fmt.Printf("Found enum value [%s] for '%s' ('%s')\n", def.Value, def.VarName, *def.Description)
|
||||
} else {
|
||||
fmt.Printf("Found enum value [%s] for '%s'\n", def.Value, def.VarName)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Printf("Found non-enum value [%s] for '%s' ( looks like enum value, but no matching @enum:type )\n", def.Value, def.VarName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enums, pkgname
|
||||
}
|
||||
|
||||
func fmtOutput(enums []EnumDef, pkgname string) string {
|
||||
str := "// Code generated by permissions_gen.sh DO NOT EDIT.\n"
|
||||
str += "\n"
|
||||
str += "package " + pkgname + "\n"
|
||||
str += "\n"
|
||||
|
||||
str += "import \"gogs.mikescher.com/BlackForestBytes/goext/langext\"" + "\n"
|
||||
str += "\n"
|
||||
|
||||
str += "type Enum interface {" + "\n"
|
||||
str += " Valid() bool" + "\n"
|
||||
str += " ValuesAny() []any" + "\n"
|
||||
str += " ValuesMeta() []EnumMetaValue" + "\n"
|
||||
str += " VarName() string" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "type StringEnum interface {" + "\n"
|
||||
str += " Enum" + "\n"
|
||||
str += " String() string" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "type DescriptionEnum interface {" + "\n"
|
||||
str += " Enum" + "\n"
|
||||
str += " Description() string" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "\n"
|
||||
|
||||
str += "type EnumMetaValue struct {" + "\n"
|
||||
str += " VarName string `json:\"varName\"`" + "\n"
|
||||
str += " Value any `json:\"value\"`" + "\n"
|
||||
str += " Description *string `json:\"description\"`" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "\n"
|
||||
|
||||
for _, enumdef := range enums {
|
||||
|
||||
hasDescr := langext.ArrAll(enumdef.Values, func(val EnumDefVal) bool { return val.Description != nil })
|
||||
hasStr := enumdef.Type == "string"
|
||||
|
||||
str += "// ================================ " + enumdef.EnumTypeName + " ================================" + "\n"
|
||||
str += "//" + "\n"
|
||||
str += "// File: " + enumdef.File + "\n"
|
||||
str += "// StringEnum: " + langext.Conditional(hasStr, "true", "false") + "\n"
|
||||
str += "// DescrEnum: " + langext.Conditional(hasDescr, "true", "false") + "\n"
|
||||
str += "//" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "var __" + enumdef.EnumTypeName + "Values = []" + enumdef.EnumTypeName + "{" + "\n"
|
||||
for _, v := range enumdef.Values {
|
||||
str += " " + v.VarName + "," + "\n"
|
||||
}
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
if hasDescr {
|
||||
str += "var __" + enumdef.EnumTypeName + "Descriptions = map[" + enumdef.EnumTypeName + "]string{" + "\n"
|
||||
for _, v := range enumdef.Values {
|
||||
str += " " + v.VarName + ": \"" + strings.TrimSpace(*v.Description) + "\"," + "\n"
|
||||
}
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
}
|
||||
|
||||
str += "var __" + enumdef.EnumTypeName + "Varnames = map[" + enumdef.EnumTypeName + "]string{" + "\n"
|
||||
for _, v := range enumdef.Values {
|
||||
str += " " + v.VarName + ": \"" + v.VarName + "\"," + "\n"
|
||||
}
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func (e " + enumdef.EnumTypeName + ") Valid() bool {" + "\n"
|
||||
str += " return langext.InArray(e, __" + enumdef.EnumTypeName + "Values)" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func (e " + enumdef.EnumTypeName + ") Values() []" + enumdef.EnumTypeName + " {" + "\n"
|
||||
str += " return __" + enumdef.EnumTypeName + "Values" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func (e " + enumdef.EnumTypeName + ") ValuesAny() []any {" + "\n"
|
||||
str += " return langext.ArrCastToAny(__" + enumdef.EnumTypeName + "Values)" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func (e " + enumdef.EnumTypeName + ") ValuesMeta() []EnumMetaValue {" + "\n"
|
||||
str += " return []EnumMetaValue{" + "\n"
|
||||
for _, v := range enumdef.Values {
|
||||
if hasDescr {
|
||||
str += " " + fmt.Sprintf("EnumMetaValue{VarName: \"%s\", Value: %s, Description: langext.Ptr(\"%s\")},", v.VarName, v.VarName, strings.TrimSpace(*v.Description)) + "\n"
|
||||
} else {
|
||||
str += " " + fmt.Sprintf("EnumMetaValue{VarName: \"%s\", Value: %s, Description: nil},", v.VarName, v.VarName) + "\n"
|
||||
}
|
||||
}
|
||||
str += " }" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
if hasStr {
|
||||
str += "func (e " + enumdef.EnumTypeName + ") String() string {" + "\n"
|
||||
str += " return string(e)" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
}
|
||||
|
||||
if hasDescr {
|
||||
str += "func (e " + enumdef.EnumTypeName + ") Description() string {" + "\n"
|
||||
str += " if d, ok := __" + enumdef.EnumTypeName + "Descriptions[e]; ok {" + "\n"
|
||||
str += " return d" + "\n"
|
||||
str += " }" + "\n"
|
||||
str += " return \"\"" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
}
|
||||
|
||||
str += "func (e " + enumdef.EnumTypeName + ") VarName() string {" + "\n"
|
||||
str += " if d, ok := __" + enumdef.EnumTypeName + "Varnames[e]; ok {" + "\n"
|
||||
str += " return d" + "\n"
|
||||
str += " }" + "\n"
|
||||
str += " return \"\"" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func Parse" + enumdef.EnumTypeName + "(vv string) (" + enumdef.EnumTypeName + ", bool) {" + "\n"
|
||||
str += " for _, ev := range __" + enumdef.EnumTypeName + "Values {" + "\n"
|
||||
str += " if string(ev) == vv {" + "\n"
|
||||
str += " return ev, true" + "\n"
|
||||
str += " }" + "\n"
|
||||
str += " }" + "\n"
|
||||
str += " return \"\", false" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func " + enumdef.EnumTypeName + "Values() []" + enumdef.EnumTypeName + " {" + "\n"
|
||||
str += " return __" + enumdef.EnumTypeName + "Values" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
str += "func " + enumdef.EnumTypeName + "ValuesMeta() []EnumMetaValue {" + "\n"
|
||||
str += " return []EnumMetaValue{" + "\n"
|
||||
for _, v := range enumdef.Values {
|
||||
if hasDescr {
|
||||
str += " " + fmt.Sprintf("EnumMetaValue{VarName: \"%s\", Value: %s, Description: langext.Ptr(\"%s\")},", v.VarName, v.VarName, strings.TrimSpace(*v.Description)) + "\n"
|
||||
} else {
|
||||
str += " " + fmt.Sprintf("EnumMetaValue{VarName: \"%s\", Value: %s, Description: nil},", v.VarName, v.VarName) + "\n"
|
||||
}
|
||||
}
|
||||
str += " }" + "\n"
|
||||
str += "}" + "\n"
|
||||
str += "" + "\n"
|
||||
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package apierr
|
||||
|
||||
type APIError int
|
||||
type APIError int //@enum:type
|
||||
|
||||
//goland:noinspection GoSnakeCaseUsage
|
||||
const (
|
||||
@ -37,11 +37,13 @@ const (
|
||||
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
||||
MESSAGE_NOT_FOUND APIError = 1305
|
||||
SUBSCRIPTION_USER_MISMATCH APIError = 1306
|
||||
KEY_NOT_FOUND APIError = 1307
|
||||
USER_AUTH_FAILED APIError = 1311
|
||||
|
||||
NO_DEVICE_LINKED APIError = 1401
|
||||
|
||||
CHANNEL_ALREADY_EXISTS APIError = 1501
|
||||
CANNOT_SELFDELETE_KEY APIError = 1511
|
||||
|
||||
QUOTA_REACHED APIError = 2101
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package apihighlight
|
||||
|
||||
type ErrHighlight int
|
||||
type ErrHighlight int //@enum:type
|
||||
|
||||
//goland:noinspection GoSnakeCaseUsage
|
||||
const (
|
||||
|
@ -108,6 +108,11 @@ func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse,
|
||||
|
||||
permObj, hasPerm := g.Get("perm")
|
||||
|
||||
hasTok := false
|
||||
if hasPerm {
|
||||
hasTok = permObj.(models.PermissionSet).Token != nil
|
||||
}
|
||||
|
||||
return models.RequestLog{
|
||||
Method: g.Request.Method,
|
||||
URI: g.Request.URL.String(),
|
||||
@ -117,8 +122,9 @@ func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse,
|
||||
RequestBodySize: int64(len(reqbody)),
|
||||
RequestContentType: ct,
|
||||
RemoteIP: g.RemoteIP(),
|
||||
UserID: langext.ConditionalFn10(hasPerm, func() *models.UserID { return permObj.(models.PermissionSet).UserID }, nil),
|
||||
Permissions: langext.ConditionalFn10(hasPerm, func() *string { return langext.Ptr(string(permObj.(models.PermissionSet).KeyType)) }, nil),
|
||||
TokenID: langext.ConditionalFn10(hasTok, func() *models.KeyTokenID { return langext.Ptr(permObj.(models.PermissionSet).Token.KeyTokenID) }, nil),
|
||||
UserID: langext.ConditionalFn10(hasTok, func() *models.UserID { return langext.Ptr(permObj.(models.PermissionSet).Token.OwnerUserID) }, nil),
|
||||
Permissions: langext.ConditionalFn10(hasTok, func() *string { return langext.Ptr(permObj.(models.PermissionSet).Token.Permissions.String()) }, nil),
|
||||
ResponseStatuscode: langext.ConditionalFn10(resp != nil, func() *int64 { return langext.Ptr(int64(resp.Statuscode())) }, nil),
|
||||
ResponseBodySize: langext.ConditionalFn10(strrespbody != nil, func() *int64 { return langext.Ptr(int64(len(*respbody))) }, nil),
|
||||
ResponseBody: strrespbody,
|
||||
|
@ -36,7 +36,7 @@ func NewAPIHandler(app *logic.Application) APIHandler {
|
||||
//
|
||||
// @Param post_body body handler.CreateUser.body false " "
|
||||
//
|
||||
// @Success 200 {object} models.UserJSONWithClients
|
||||
// @Success 200 {object} models.UserJSONWithClientsAndKeys
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
@ -111,13 +111,28 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
username = langext.Ptr(h.app.NormalizeUsername(*username))
|
||||
}
|
||||
|
||||
userobj, err := h.database.CreateUser(ctx, readKey, sendKey, adminKey, b.ProToken, username)
|
||||
userobj, err := h.database.CreateUser(ctx, b.ProToken, username)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
||||
}
|
||||
|
||||
_, err = h.database.CreateKeyToken(ctx, "AdminKey (default)", userobj.UserID, true, make([]models.ChannelID, 0), models.TokenPermissionList{models.PermAdmin}, adminKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create admin-key in db", err)
|
||||
}
|
||||
|
||||
_, err = h.database.CreateKeyToken(ctx, "SendKey (default)", userobj.UserID, true, make([]models.ChannelID, 0), models.TokenPermissionList{models.PermChannelSend}, sendKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create send-key in db", err)
|
||||
}
|
||||
|
||||
_, err = h.database.CreateKeyToken(ctx, "ReadKey (default)", userobj.UserID, true, make([]models.ChannelID, 0), models.TokenPermissionList{models.PermUserRead, models.PermChannelRead}, readKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create read-key in db", err)
|
||||
}
|
||||
|
||||
if b.NoClient {
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients(make([]models.Client, 0))))
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients(make([]models.Client, 0), adminKey, sendKey, readKey)))
|
||||
} else {
|
||||
err := h.database.DeleteClientsByFCM(ctx, b.FCMToken)
|
||||
if err != nil {
|
||||
@ -129,7 +144,7 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create client in db", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients([]models.Client{client})))
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients([]models.Client{client}, adminKey, sendKey, readKey)))
|
||||
}
|
||||
|
||||
}
|
||||
@ -205,9 +220,6 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
type body struct {
|
||||
Username *string `json:"username"`
|
||||
ProToken *string `json:"pro_token"`
|
||||
RefreshReadKey *bool `json:"read_key"`
|
||||
RefreshSendKey *bool `json:"send_key"`
|
||||
RefreshAdminKey *bool `json:"admin_key"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
@ -262,33 +274,6 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
}
|
||||
|
||||
if langext.Coalesce(b.RefreshSendKey, false) {
|
||||
newkey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err := h.database.UpdateUserSendKey(ctx, u.UserID, newkey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
||||
}
|
||||
}
|
||||
|
||||
if langext.Coalesce(b.RefreshReadKey, false) {
|
||||
newkey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err := h.database.UpdateUserReadKey(ctx, u.UserID, newkey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
||||
}
|
||||
}
|
||||
|
||||
if langext.Coalesce(b.RefreshAdminKey, false) {
|
||||
newkey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err := h.database.UpdateUserAdminKey(ctx, u.UserID, newkey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
||||
}
|
||||
}
|
||||
|
||||
user, err := h.database.GetUser(ctx, u.UserID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query (updated) user", err)
|
||||
@ -705,9 +690,8 @@ func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
|
||||
subscribeKey := h.app.GenerateRandomAuthKey()
|
||||
sendKey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
channel, err := h.database.CreateChannel(ctx, u.UserID, channelDisplayName, channelInternalName, subscribeKey, sendKey)
|
||||
channel, err := h.database.CreateChannel(ctx, u.UserID, channelDisplayName, channelInternalName, subscribeKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create channel", err)
|
||||
}
|
||||
@ -756,7 +740,6 @@ func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
type body struct {
|
||||
RefreshSubscribeKey *bool `json:"subscribe_key"`
|
||||
RefreshSendKey *bool `json:"send_key"`
|
||||
DisplayName *string `json:"display_name"`
|
||||
DescriptionName *string `json:"description_name"`
|
||||
}
|
||||
@ -789,15 +772,6 @@ func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query user", err)
|
||||
}
|
||||
|
||||
if langext.Coalesce(b.RefreshSendKey, false) {
|
||||
newkey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err := h.database.UpdateChannelSendKey(ctx, u.ChannelID, newkey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update channel", err)
|
||||
}
|
||||
}
|
||||
|
||||
if langext.Coalesce(b.RefreshSubscribeKey, false) {
|
||||
newkey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
@ -905,10 +879,6 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
pageSize := mathext.Clamp(langext.Coalesce(q.PageSize, 64), 1, maxPageSize)
|
||||
|
||||
if permResp := ctx.CheckPermissionRead(); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
channel, err := h.database.GetChannel(ctx, u.ChannelUserID, u.ChannelID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.APIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||
@ -917,17 +887,8 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
}
|
||||
|
||||
userid := *ctx.GetPermissionUserID()
|
||||
|
||||
sub, err := h.database.GetSubscriptionBySubscriber(ctx, userid, channel.ChannelID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
if !sub.Confirmed {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
if permResp := ctx.CheckPermissionChanMessagesRead(channel.Channel); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
tok, err := ct.Decode(langext.Coalesce(q.NextPageToken, ""))
|
||||
@ -1413,7 +1374,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
pageSize := mathext.Clamp(langext.Coalesce(q.PageSize, 64), 1, maxPageSize)
|
||||
|
||||
if permResp := ctx.CheckPermissionRead(); permResp != nil {
|
||||
if permResp := ctx.CheckPermissionSelfAllMessagesRead(); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
@ -1494,12 +1455,14 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||
}
|
||||
|
||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||
|
||||
// either we have direct read permissions (it is our message + read/admin key)
|
||||
// or we subscribe (+confirmed) to the channel and have read/admin key
|
||||
|
||||
if uid := ctx.GetPermissionUserID(); uid != nil && ctx.IsPermissionUserRead() {
|
||||
if ctx.CheckPermissionMessageRead(msg) {
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
||||
}
|
||||
|
||||
if uid := ctx.GetPermissionUserID(); uid != nil && ctx.CheckPermissionUserRead(*uid) == nil {
|
||||
sub, err := h.database.GetSubscriptionBySubscriber(ctx, *uid, msg.ChannelID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
@ -1512,16 +1475,12 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
// sub not confirmed
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
// => perm okay
|
||||
|
||||
} else {
|
||||
// auth-key is not set or not a user:x variant
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
||||
}
|
||||
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
// DeleteMessage swaggerdoc
|
||||
@ -1564,7 +1523,7 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||
}
|
||||
|
||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||
if !ctx.CheckPermissionMessageRead(msg) {
|
||||
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
@ -1580,3 +1539,284 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
||||
}
|
||||
|
||||
// ListUserKeys swaggerdoc
|
||||
//
|
||||
// @Summary List keys of the user
|
||||
// @Description The request must be done with an ADMIN key, the returned keys are without their token.
|
||||
// @ID api-tokenkeys-list
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
//
|
||||
// @Success 200 {object} handler.ListUserKeys.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/users/:uid/keys [GET]
|
||||
func (h APIHandler) ListUserKeys(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||
}
|
||||
type response struct {
|
||||
Tokens []models.KeyTokenJSON `json:"tokens"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
clients, err := h.database.ListKeyTokens(ctx, u.UserID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query keys", err)
|
||||
}
|
||||
|
||||
res := langext.ArrMap(clients, func(v models.KeyToken) models.KeyTokenJSON { return v.JSON() })
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Tokens: res}))
|
||||
}
|
||||
|
||||
// GetUserKey swaggerdoc
|
||||
//
|
||||
// @Summary Get a single key
|
||||
// @Description The request must be done with an ADMIN key, the returned key does not include its token.
|
||||
// @ID api-tokenkeys-get
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param kid path int true "TokenKeyID"
|
||||
//
|
||||
// @Success 200 {object} models.KeyTokenJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/users/:uid/keys/:kid [GET]
|
||||
func (h APIHandler) GetUserKey(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||
KeyID models.KeyTokenID `uri:"kid" binding:"entityid"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
client, err := h.database.GetKeyToken(ctx, u.UserID, u.KeyID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||
}
|
||||
|
||||
// UpdateUserKey swaggerdoc
|
||||
//
|
||||
// @Summary Update a key
|
||||
// @ID api-tokenkeys-update
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param kid path int true "TokenKeyID"
|
||||
//
|
||||
// @Success 200 {object} models.KeyTokenJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/users/:uid/keys/:kid [PATCH]
|
||||
func (h APIHandler) UpdateUserKey(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||
KeyID models.KeyTokenID `uri:"kid" binding:"entityid"`
|
||||
}
|
||||
type body struct {
|
||||
Name *string `json:"name"`
|
||||
AllChannels *bool `json:"all_channels"`
|
||||
Channels *[]models.ChannelID `json:"channels"`
|
||||
Permissions *string `json:"permissions"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
var b body
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
client, err := h.database.GetKeyToken(ctx, u.UserID, u.KeyID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||
}
|
||||
|
||||
if b.Name != nil {
|
||||
err := h.database.UpdateKeyTokenName(ctx, u.KeyID, *b.Name)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update name", err)
|
||||
}
|
||||
}
|
||||
|
||||
if b.Permissions != nil {
|
||||
err := h.database.UpdateKeyTokenPermissions(ctx, u.KeyID, models.ParseTokenPermissionList(*b.Permissions))
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update permissions", err)
|
||||
}
|
||||
}
|
||||
|
||||
if b.AllChannels != nil {
|
||||
err := h.database.UpdateKeyTokenAllChannels(ctx, u.KeyID, *b.AllChannels)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update all_channels", err)
|
||||
}
|
||||
}
|
||||
|
||||
if b.Channels != nil {
|
||||
err := h.database.UpdateKeyTokenChannels(ctx, u.KeyID, *b.Channels)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update channels", err)
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||
}
|
||||
|
||||
// CreateUserKey swaggerdoc
|
||||
//
|
||||
// @Summary Create a new key
|
||||
// @ID api-tokenkeys-create
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
//
|
||||
// @Param post_body body handler.CreateUserKey.body false " "
|
||||
//
|
||||
// @Success 200 {object} models.KeyTokenJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/users/:uid/keys [POST]
|
||||
func (h APIHandler) CreateUserKey(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||
}
|
||||
type body struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
AllChannels *bool `json:"all_channels" binding:"required"`
|
||||
Channels *[]models.ChannelID `json:"channels" binding:"required"`
|
||||
Permissions *string `json:"permissions" binding:"required"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
var b body
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
for _, c := range *b.Channels {
|
||||
if err := c.Valid(); err != nil {
|
||||
return ginresp.APIError(g, 400, apierr.INVALID_BODY_PARAM, "Invalid ChannelID", err)
|
||||
}
|
||||
}
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
token := h.app.GenerateRandomAuthKey()
|
||||
|
||||
perms := models.ParseTokenPermissionList(*b.Permissions)
|
||||
|
||||
keytok, err := h.database.CreateKeyToken(ctx, b.Name, *ctx.GetPermissionUserID(), *b.AllChannels, *b.Channels, perms, token)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create keytoken in db", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, keytok.JSON().WithToken(token)))
|
||||
}
|
||||
|
||||
// DeleteUserKey swaggerdoc
|
||||
//
|
||||
// @Summary Delete a key
|
||||
// @Description Cannot be used to delete the key used in the request itself
|
||||
// @ID api-tokenkeys-delete
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param kid path int true "TokenKeyID"
|
||||
//
|
||||
// @Success 200 {object} models.KeyTokenJSON
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/users/:uid/keys/:kid [DELETE]
|
||||
func (h APIHandler) DeleteUserKey(g *gin.Context) ginresp.HTTPResponse {
|
||||
type uri struct {
|
||||
UserID models.UserID `uri:"uid" binding:"entityid"`
|
||||
KeyID models.KeyTokenID `uri:"kid" binding:"entityid"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
client, err := h.database.GetKeyToken(ctx, u.UserID, u.KeyID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.APIError(g, 404, apierr.KEY_NOT_FOUND, "Key not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||
}
|
||||
|
||||
if u.KeyID == *ctx.GetPermissionKeyTokenID() {
|
||||
return ginresp.APIError(g, 404, apierr.CANNOT_SELFDELETE_KEY, "Cannot delete the currently used key", err)
|
||||
}
|
||||
|
||||
err = h.database.DeleteKeyToken(ctx, u.KeyID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete client", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
|
||||
}
|
||||
|
||||
okResp, errResp := h.sendMessageInternal(g, ctx, langext.Ptr(models.UserID(*newid)), data.UserKey, nil, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil)
|
||||
okResp, errResp := h.sendMessageInternal(g, ctx, langext.Ptr(models.UserID(*newid)), data.UserKey, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
} else {
|
||||
@ -195,8 +195,6 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
}
|
||||
|
||||
readKey := h.app.GenerateRandomAuthKey()
|
||||
sendKey := h.app.GenerateRandomAuthKey()
|
||||
adminKey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err := h.database.ClearFCMTokens(ctx, *data.FCMToken)
|
||||
@ -211,11 +209,16 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
}
|
||||
|
||||
user, err := h.database.CreateUser(ctx, readKey, sendKey, adminKey, data.ProToken, nil)
|
||||
user, err := h.database.CreateUser(ctx, data.ProToken, nil)
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to create user in db")
|
||||
}
|
||||
|
||||
_, err = h.database.CreateKeyToken(ctx, "CompatKey", user.UserID, true, make([]models.ChannelID, 0), models.TokenPermissionList{models.PermAdmin}, adminKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create admin-key in db", err)
|
||||
}
|
||||
|
||||
_, err = h.database.CreateClient(ctx, user.UserID, models.ClientTypeAndroid, *data.FCMToken, "compat", "compat")
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to create client in db")
|
||||
@ -230,7 +233,7 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse {
|
||||
Success: true,
|
||||
Message: "New user registered",
|
||||
UserID: oldid,
|
||||
UserKey: user.AdminKey,
|
||||
UserKey: adminKey,
|
||||
QuotaUsed: user.QuotaUsedToday(),
|
||||
QuotaMax: user.QuotaPerDay(),
|
||||
IsPro: user.IsPro,
|
||||
@ -305,7 +308,14 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
@ -320,7 +330,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
|
||||
Success: true,
|
||||
Message: "ok",
|
||||
UserID: *data.UserID,
|
||||
UserKey: user.AdminKey,
|
||||
UserKey: keytok.Token,
|
||||
QuotaUsed: user.QuotaUsedToday(),
|
||||
QuotaMax: user.QuotaPerDay(),
|
||||
IsPro: langext.Conditional(user.IsPro, 1, 0),
|
||||
@ -398,7 +408,14 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
@ -493,7 +510,14 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
@ -603,7 +627,14 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
@ -613,10 +644,13 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
|
||||
newAdminKey := h.app.GenerateRandomAuthKey()
|
||||
newReadKey := h.app.GenerateRandomAuthKey()
|
||||
newSendKey := h.app.GenerateRandomAuthKey()
|
||||
|
||||
err = h.database.UpdateUserKeys(ctx, user.UserID, newSendKey, newReadKey, newAdminKey)
|
||||
_, err = h.database.CreateKeyToken(ctx, "CompatKey", user.UserID, true, make([]models.ChannelID, 0), models.TokenPermissionList{models.PermAdmin}, newAdminKey)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create admin-key in db", err)
|
||||
}
|
||||
|
||||
err = h.database.DeleteKeyToken(ctx, keytok.KeyTokenID)
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to update keys")
|
||||
}
|
||||
@ -648,7 +682,7 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
|
||||
Success: true,
|
||||
Message: "user updated",
|
||||
UserID: *data.UserID,
|
||||
UserKey: user.AdminKey,
|
||||
UserKey: newAdminKey,
|
||||
QuotaUsed: user.QuotaUsedToday(),
|
||||
QuotaMax: user.QuotaPerDay(),
|
||||
IsPro: langext.Conditional(user.IsPro, 1, 0),
|
||||
@ -723,7 +757,14 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
@ -835,7 +876,14 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.CompatAPIError(0, "Failed to query user")
|
||||
}
|
||||
|
||||
if user.AdminKey != *data.UserKey {
|
||||
keytok, err := h.database.GetKeyTokenByToken(ctx, *data.UserKey)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.CompatAPIError(0, "Failed to query token")
|
||||
}
|
||||
if !keytok.IsAdmin(user.UserID) {
|
||||
return ginresp.CompatAPIError(204, "Authentification failed")
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,8 @@ func NewMessageHandler(app *logic.Application) MessageHandler {
|
||||
func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
type combined struct {
|
||||
UserID *models.UserID `json:"user_id" form:"user_id" example:"7725" `
|
||||
UserKey *string `json:"user_key" form:"user_key" example:"P3TNH8mvv14fm" `
|
||||
KeyToken *string `json:"key" form:"key" example:"P3TNH8mvv14fm" `
|
||||
Channel *string `json:"channel" form:"channel" example:"test" `
|
||||
ChanKey *string `json:"chan_key" form:"chan_key" example:"qhnUbKcLgp6tg" `
|
||||
Title *string `json:"title" form:"title" example:"Hello World" `
|
||||
Content *string `json:"content" form:"content" example:"This is a message" `
|
||||
Priority *int `json:"priority" form:"priority" example:"1" enums:"0,1,2" `
|
||||
@ -95,7 +94,7 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
// query has highest prio, then form, then json
|
||||
data := dataext.ObjectMerge(dataext.ObjectMerge(b, f), q)
|
||||
|
||||
okResp, errResp := h.sendMessageInternal(g, ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, data.SenderName)
|
||||
okResp, errResp := h.sendMessageInternal(g, ctx, data.UserID, data.KeyToken, data.Channel, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, data.SenderName)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
} else {
|
||||
@ -129,7 +128,7 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
}
|
||||
|
||||
func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContext, UserID *models.UserID, UserKey *string, Channel *string, ChanKey *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64, SenderName *string) (*SendMessageResponse, *ginresp.HTTPResponse) {
|
||||
func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContext, UserID *models.UserID, Key *string, Channel *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64, SenderName *string) (*SendMessageResponse, *ginresp.HTTPResponse) {
|
||||
if Title != nil {
|
||||
Title = langext.Ptr(strings.TrimSpace(*Title))
|
||||
}
|
||||
@ -140,8 +139,8 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
|
||||
if UserID == nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_UID, hl.USER_ID, "Missing parameter [[user_id]]", nil))
|
||||
}
|
||||
if UserKey == nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_TOK, hl.USER_KEY, "Missing parameter [[user_token]]", nil))
|
||||
if Key == nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_TOK, hl.USER_KEY, "Missing parameter [[key]]", nil))
|
||||
}
|
||||
if Title == nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.MISSING_TITLE, hl.TITLE, "Missing parameter [[title]]", nil))
|
||||
@ -224,32 +223,13 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 403, apierr.QUOTA_REACHED, hl.NONE, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil))
|
||||
}
|
||||
|
||||
var channel models.Channel
|
||||
if ChanKey != nil {
|
||||
// foreign channel (+ channel send-key)
|
||||
|
||||
foreignChan, err := h.database.GetChannelByNameAndSendKey(ctx, channelInternalName, *ChanKey)
|
||||
if err != nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query (foreign) channel", err))
|
||||
}
|
||||
if foreignChan == nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 400, apierr.CHANNEL_NOT_FOUND, hl.CHANNEL, "(Foreign) Channel not found", err))
|
||||
}
|
||||
channel = *foreignChan
|
||||
} else {
|
||||
// own channel
|
||||
|
||||
channel, err = h.app.GetOrCreateChannel(ctx, *UserID, channelDisplayName, channelInternalName)
|
||||
channel, err := h.app.GetOrCreateChannel(ctx, *UserID, channelDisplayName, channelInternalName)
|
||||
if err != nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query/create (owned) channel", err))
|
||||
}
|
||||
}
|
||||
|
||||
selfChanAdmin := *UserID == channel.OwnerUserID && *UserKey == user.AdminKey
|
||||
selfChanSend := *UserID == channel.OwnerUserID && *UserKey == user.SendKey
|
||||
forgChanSend := *UserID != channel.OwnerUserID && ChanKey != nil && *ChanKey == channel.SendKey
|
||||
|
||||
if !selfChanAdmin && !selfChanSend && !forgChanSend {
|
||||
keytok, permResp := ctx.CheckPermissionSend(channel, *Key)
|
||||
if permResp != nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 401, apierr.USER_AUTH_FAILED, hl.USER_KEY, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
@ -287,6 +267,11 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc channel msg-counter", err))
|
||||
}
|
||||
|
||||
err = h.database.IncKeyTokenMessageCounter(ctx, keytok.KeyTokenID)
|
||||
if err != nil {
|
||||
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err))
|
||||
}
|
||||
|
||||
for _, sub := range subscriptions {
|
||||
clients, err := h.database.ListClients(ctx, sub.SubscriberUserID)
|
||||
if err != nil {
|
||||
|
@ -127,6 +127,12 @@ func (r *Router) Init(e *gin.Engine) error {
|
||||
apiv2.GET("/users/:uid", r.Wrap(r.apiHandler.GetUser))
|
||||
apiv2.PATCH("/users/:uid", r.Wrap(r.apiHandler.UpdateUser))
|
||||
|
||||
apiv2.GET("/users/:uid/keys", r.Wrap(r.apiHandler.ListUserKeys))
|
||||
apiv2.POST("/users/:uid/keys", r.Wrap(r.apiHandler.CreateUserKey))
|
||||
apiv2.GET("/users/:uid/keys/:kid", r.Wrap(r.apiHandler.GetUserKey))
|
||||
apiv2.PATCH("/users/:uid/keys/:kid", r.Wrap(r.apiHandler.UpdateUserKey))
|
||||
apiv2.DELETE("/users/:uid/keys/:kid", r.Wrap(r.apiHandler.DeleteUserKey))
|
||||
|
||||
apiv2.GET("/users/:uid/clients", r.Wrap(r.apiHandler.ListClients))
|
||||
apiv2.GET("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.GetClient))
|
||||
apiv2.POST("/users/:uid/clients", r.Wrap(r.apiHandler.AddClient))
|
||||
|
@ -12,36 +12,36 @@ import (
|
||||
|
||||
type Config struct {
|
||||
Namespace string
|
||||
BaseURL string `env:"SCN_URL"`
|
||||
GinDebug bool `env:"SCN_GINDEBUG"`
|
||||
LogLevel zerolog.Level `env:"SCN_LOGLEVEL"`
|
||||
ServerIP string `env:"SCN_IP"`
|
||||
ServerPort string `env:"SCN_PORT"`
|
||||
DBMain DBConfig `env:"SCN_DB_MAIN"`
|
||||
DBRequests DBConfig `env:"SCN_DB_REQUESTS"`
|
||||
DBLogs DBConfig `env:"SCN_DB_LOGS"`
|
||||
RequestTimeout time.Duration `env:"SCN_REQUEST_TIMEOUT"`
|
||||
RequestMaxRetry int `env:"SCN_REQUEST_MAXRETRY"`
|
||||
RequestRetrySleep time.Duration `env:"SCN_REQUEST_RETRYSLEEP"`
|
||||
Cors bool `env:"SCN_CORS"`
|
||||
ReturnRawErrors bool `env:"SCN_ERROR_RETURN"`
|
||||
DummyFirebase bool `env:"SCN_DUMMY_FB"`
|
||||
DummyGoogleAPI bool `env:"SCN_DUMMY_GOOG"`
|
||||
FirebaseTokenURI string `env:"SCN_FB_TOKENURI"`
|
||||
FirebaseProjectID string `env:"SCN_FB_PROJECTID"`
|
||||
FirebasePrivKeyID string `env:"SCN_FB_PRIVATEKEYID"`
|
||||
FirebaseClientMail string `env:"SCN_FB_CLIENTEMAIL"`
|
||||
FirebasePrivateKey string `env:"SCN_FB_PRIVATEKEY"`
|
||||
GoogleAPITokenURI string `env:"SCN_GOOG_TOKENURI"`
|
||||
GoogleAPIPrivKeyID string `env:"SCN_GOOG_PRIVATEKEYID"`
|
||||
GoogleAPIClientMail string `env:"SCN_GOOG_CLIENTEMAIL"`
|
||||
GoogleAPIPrivateKey string `env:"SCN_GOOG_PRIVATEKEY"`
|
||||
GooglePackageName string `env:"SCN_GOOG_PACKAGENAME"`
|
||||
GoogleProProductID string `env:"SCN_GOOG_PROPRODUCTID"`
|
||||
ReqLogEnabled bool `env:"SCN_REQUESTLOG_ENABLED"`
|
||||
ReqLogMaxBodySize int `env:"SCN_REQUESTLOG_MAXBODYSIZE"`
|
||||
ReqLogHistoryMaxCount int `env:"SCN_REQUESTLOG_HISTORY_MAXCOUNT"`
|
||||
ReqLogHistoryMaxDuration time.Duration `env:"SCN_REQUESTLOG_HISTORY_MAXDURATION"`
|
||||
BaseURL string `env:"URL"`
|
||||
GinDebug bool `env:"GINDEBUG"`
|
||||
LogLevel zerolog.Level `env:"LOGLEVEL"`
|
||||
ServerIP string `env:"IP"`
|
||||
ServerPort string `env:"PORT"`
|
||||
DBMain DBConfig `env:"DB_MAIN"`
|
||||
DBRequests DBConfig `env:"DB_REQUESTS"`
|
||||
DBLogs DBConfig `env:"DB_LOGS"`
|
||||
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT"`
|
||||
RequestMaxRetry int `env:"REQUEST_MAXRETRY"`
|
||||
RequestRetrySleep time.Duration `env:"REQUEST_RETRYSLEEP"`
|
||||
Cors bool `env:"CORS"`
|
||||
ReturnRawErrors bool `env:"ERROR_RETURN"`
|
||||
DummyFirebase bool `env:"DUMMY_FB"`
|
||||
DummyGoogleAPI bool `env:"DUMMY_GOOG"`
|
||||
FirebaseTokenURI string `env:"FB_TOKENURI"`
|
||||
FirebaseProjectID string `env:"FB_PROJECTID"`
|
||||
FirebasePrivKeyID string `env:"FB_PRIVATEKEYID"`
|
||||
FirebaseClientMail string `env:"FB_CLIENTEMAIL"`
|
||||
FirebasePrivateKey string `env:"FB_PRIVATEKEY"`
|
||||
GoogleAPITokenURI string `env:"GOOG_TOKENURI"`
|
||||
GoogleAPIPrivKeyID string `env:"GOOG_PRIVATEKEYID"`
|
||||
GoogleAPIClientMail string `env:"GOOG_CLIENTEMAIL"`
|
||||
GoogleAPIPrivateKey string `env:"GOOG_PRIVATEKEY"`
|
||||
GooglePackageName string `env:"GOOG_PACKAGENAME"`
|
||||
GoogleProProductID string `env:"GOOG_PROPRODUCTID"`
|
||||
ReqLogEnabled bool `env:"REQUESTLOG_ENABLED"`
|
||||
ReqLogMaxBodySize int `env:"REQUESTLOG_MAXBODYSIZE"`
|
||||
ReqLogHistoryMaxCount int `env:"REQUESTLOG_HISTORY_MAXCOUNT"`
|
||||
ReqLogHistoryMaxDuration time.Duration `env:"REQUESTLOG_HISTORY_MAXDURATION"`
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
@ -430,7 +430,7 @@ func GetConfig(ns string) (Config, bool) {
|
||||
}
|
||||
if cfn, ok := allConfig[ns]; ok {
|
||||
c := cfn()
|
||||
err := confext.ApplyEnvOverrides(&c, "_")
|
||||
err := confext.ApplyEnvOverrides("SCN_", &c, "_")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Mode string
|
||||
type Mode string //@enum:type
|
||||
|
||||
const (
|
||||
CTMStart = "START"
|
||||
|
@ -32,31 +32,6 @@ func (db *Database) GetChannelByName(ctx TxContext, userid models.UserID, chanNa
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByNameAndSendKey(ctx TxContext, chanName string, sendKey string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE internal_name = :chan_name OR send_key = :send_key LIMIT 1", sq.PP{
|
||||
"chan_name": chanName,
|
||||
"send_key": sendKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByID(ctx TxContext, chanid models.ChannelID) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
@ -81,7 +56,7 @@ func (db *Database) GetChannelByID(ctx TxContext, chanid models.ChannelID) (*mod
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName string, intName string, subscribeKey string, sendKey string) (models.Channel, error) {
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName string, intName string, subscribeKey string) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
@ -91,14 +66,13 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
|
||||
|
||||
channelid := models.NewChannelID()
|
||||
|
||||
_, err = tx.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, description_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :hnam, :subkey, :sendkey, :ts)", sq.PP{
|
||||
_, err = tx.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, description_name, subscribe_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :hnam, :subkey, :ts)", sq.PP{
|
||||
"cid": channelid,
|
||||
"ouid": userid,
|
||||
"dnam": dispName,
|
||||
"inam": intName,
|
||||
"hnam": nil,
|
||||
"subkey": subscribeKey,
|
||||
"sendkey": sendKey,
|
||||
"ts": time2DB(now),
|
||||
})
|
||||
if err != nil {
|
||||
@ -111,7 +85,6 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
|
||||
DisplayName: dispName,
|
||||
InternalName: intName,
|
||||
SubscribeKey: subscribeKey,
|
||||
SendKey: sendKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
@ -244,23 +217,6 @@ func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Chann
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateChannelSendKey(ctx TxContext, channelid models.ChannelID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE channels SET send_key = :key WHERE channel_id = :cid", sq.PP{
|
||||
"key": newkey,
|
||||
"cid": channelid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateChannelSubscribeKey(ctx TxContext, channelid models.ChannelID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
|
227
scnserver/db/impl/primary/keytokens.go
Normal file
227
scnserver/db/impl/primary/keytokens.go
Normal file
@ -0,0 +1,227 @@
|
||||
package primary
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateKeyToken(ctx TxContext, name string, owner models.UserID, allChannels bool, channels []models.ChannelID, permissions models.TokenPermissionList, token string) (models.KeyToken, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.KeyToken{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
keyTokenid := models.NewKeyTokenID()
|
||||
|
||||
_, err = tx.Exec(ctx, "INSERT INTO keytokens (keytoken_id, name, timestamp_created, owner_user_id, all_channels, channels, token, permissions) VALUES (:tid, :nam, :tsc, :owr, :all, :cha, :tok, :prm)", sq.PP{
|
||||
"tid": keyTokenid,
|
||||
"nam": name,
|
||||
"tsc": time2DB(now),
|
||||
"owr": owner.String(),
|
||||
"all": bool2DB(allChannels),
|
||||
"cha": strings.Join(langext.ArrMap(channels, func(v models.ChannelID) string { return v.String() }), ";"),
|
||||
"tok": token,
|
||||
"prm": permissions.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return models.KeyToken{}, err
|
||||
}
|
||||
|
||||
return models.KeyToken{
|
||||
KeyTokenID: keyTokenid,
|
||||
Name: name,
|
||||
TimestampCreated: now,
|
||||
TimestampLastUsed: nil,
|
||||
OwnerUserID: owner,
|
||||
AllChannels: allChannels,
|
||||
Channels: channels,
|
||||
Token: token,
|
||||
Permissions: permissions,
|
||||
MessagesSent: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListKeyTokens(ctx TxContext, ownerID models.UserID) ([]models.KeyToken, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM keytokens WHERE owner_user_id = :uid ORDER BY keytokens.timestamp_created DESC, keytokens.keytoken_id ASC", sq.PP{"uid": ownerID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeKeyTokens(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetKeyToken(ctx TxContext, userid models.UserID, keyTokenid models.KeyTokenID) (models.KeyToken, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.KeyToken{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM keytokens WHERE owner_user_id = :uid AND keytoken_id = :cid LIMIT 1", sq.PP{
|
||||
"uid": userid,
|
||||
"cid": keyTokenid,
|
||||
})
|
||||
if err != nil {
|
||||
return models.KeyToken{}, err
|
||||
}
|
||||
|
||||
keyToken, err := models.DecodeKeyToken(rows)
|
||||
if err != nil {
|
||||
return models.KeyToken{}, err
|
||||
}
|
||||
|
||||
return keyToken, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetKeyTokenByToken(ctx TxContext, key string) (*models.KeyToken, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM keytokens WHERE token = :key LIMIT 1", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeKeyToken(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteKeyToken(ctx TxContext, keyTokenid models.KeyTokenID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "DELETE FROM keytokens WHERE keytoken_id = :tid", sq.PP{"tid": keyTokenid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateKeyTokenName(ctx TxContext, keyTokenid models.KeyTokenID, name string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET name = :nam WHERE keytoken_id = :tid", sq.PP{
|
||||
"nam": name,
|
||||
"tid": keyTokenid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateKeyTokenPermissions(ctx TxContext, keyTokenid models.KeyTokenID, perm models.TokenPermissionList) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET permissions = :prm WHERE keytoken_id = :tid", sq.PP{
|
||||
"tid": keyTokenid,
|
||||
"prm": perm.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateKeyTokenAllChannels(ctx TxContext, keyTokenid models.KeyTokenID, allChannels bool) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET all_channels = :all WHERE keytoken_id = :tid", sq.PP{
|
||||
"tid": keyTokenid,
|
||||
"all": bool2DB(allChannels),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateKeyTokenChannels(ctx TxContext, keyTokenid models.KeyTokenID, channels []models.ChannelID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET channels = :cha WHERE keytoken_id = :tid", sq.PP{
|
||||
"tid": keyTokenid,
|
||||
"cha": strings.Join(langext.ArrMap(channels, func(v models.ChannelID) string { return v.String() }), ";"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) IncKeyTokenMessageCounter(ctx TxContext, keyTokenid models.KeyTokenID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET messages_sent = messages_sent + 1, timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{
|
||||
"ts": time2DB(time.Now()),
|
||||
"tid": keyTokenid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateKeyTokenLastUsed(ctx TxContext, keyTokenid models.KeyTokenID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE keytokens SET timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{
|
||||
"ts": time2DB(time.Now()),
|
||||
"tid": keyTokenid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -4,10 +4,6 @@ CREATE TABLE users
|
||||
|
||||
username TEXT NULL DEFAULT NULL,
|
||||
|
||||
send_key TEXT NOT NULL,
|
||||
read_key TEXT NOT NULL,
|
||||
admin_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastread INTEGER NULL DEFAULT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||
@ -25,6 +21,29 @@ CREATE TABLE users
|
||||
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL;
|
||||
|
||||
|
||||
CREATE TABLE keytokens
|
||||
(
|
||||
keytoken_id TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastused INTEGER NULL DEFAULT NULL,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
owner_user_id TEXT NOT NULL,
|
||||
|
||||
all_channels INTEGER CHECK(all_channels IN (0, 1)) NOT NULL,
|
||||
channels TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
permissions TEXT NOT NULL,
|
||||
|
||||
messages_sent INTEGER NOT NULL DEFAULT '0',
|
||||
|
||||
PRIMARY KEY (keytoken_id)
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX "idx_keytokens_token" ON keytokens (token);
|
||||
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_id TEXT NOT NULL,
|
||||
@ -55,7 +74,6 @@ CREATE TABLE channels
|
||||
description_name TEXT NULL,
|
||||
|
||||
subscribe_key TEXT NOT NULL,
|
||||
send_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||
|
@ -3,12 +3,11 @@ package primary
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, adminKey string, protoken *string, username *string) (models.User, error) {
|
||||
func (db *Database) CreateUser(ctx TxContext, protoken *string, username *string) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
@ -18,12 +17,9 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
|
||||
|
||||
userid := models.NewUserID()
|
||||
|
||||
_, err = tx.Exec(ctx, "INSERT INTO users (user_id, username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (:uid, :un, :rk, :sk, :ak, :pro, :tok, :ts)", sq.PP{
|
||||
_, err = tx.Exec(ctx, "INSERT INTO users (user_id, username, is_pro, pro_token, timestamp_created) VALUES (:uid, :un, :pro, :tok, :ts)", sq.PP{
|
||||
"uid": userid,
|
||||
"un": username,
|
||||
"rk": readKey,
|
||||
"sk": sendKey,
|
||||
"ak": adminKey,
|
||||
"pro": bool2DB(protoken != nil),
|
||||
"tok": protoken,
|
||||
"ts": time2DB(now),
|
||||
@ -35,9 +31,6 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
|
||||
return models.User{
|
||||
UserID: userid,
|
||||
Username: username,
|
||||
ReadKey: readKey,
|
||||
SendKey: sendKey,
|
||||
AdminKey: adminKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
@ -63,28 +56,6 @@ func (db *Database) ClearProTokens(ctx TxContext, protoken string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUserByKey(ctx TxContext, key string) (*models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM users WHERE admin_key = :key OR send_key = :key OR read_key = :key LIMIT 1", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUser(ctx TxContext, userid models.UserID) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
@ -177,73 +148,3 @@ func (db *Database) UpdateUserLastRead(ctx TxContext, userid models.UserID) erro
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserKeys(ctx TxContext, userid models.UserID, sendKey string, readKey string, adminKey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET send_key = :sk, read_key = :rk, admin_key = :ak WHERE user_id = :uid", sq.PP{
|
||||
"sk": sendKey,
|
||||
"rk": readKey,
|
||||
"ak": adminKey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserSendKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET send_key = :sk WHERE user_id = :uid", sq.PP{
|
||||
"sk": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserReadKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET read_key = :rk WHERE user_id = :uid", sq.PP{
|
||||
"rk": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserAdminKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET admin_key = :ak WHERE user_id = :uid", sq.PP{
|
||||
"ak": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ require (
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/rs/zerolog v1.28.0
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.59
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.103
|
||||
gopkg.in/loremipsum.v1 v1.1.0
|
||||
)
|
||||
|
||||
|
@ -81,6 +81,8 @@ gogs.mikescher.com/BlackForestBytes/goext v0.0.58 h1:W53yfHhpFQS13zgtzCjfJQ42WG0
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.58/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.59 h1:3bHSjqgty9yp0EIyqwGAb06ZS7bLvm806zRj6j+WOEE=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.59/go.mod h1:ZEXyKUr8t0EKdPN1FYdk0klY7N8OwXxipGE9lWgpVE8=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.103 h1:CkRVpRrTlq9k3mdTNGQAr4cxaXHsKdUJNjHt5Maas4k=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.103/go.mod h1:w8JlyUHpoOJmW5GxsiheZkFh3vn8Mp80ynSVOFLszL0=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
|
@ -35,7 +35,7 @@ func NewAndroidPublisherAPI(conf scn.Config) (AndroidPublisherClient, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PurchaseType int
|
||||
type PurchaseType int //@enum:type
|
||||
|
||||
const (
|
||||
PurchaseTypeTest PurchaseType = 0 // i.e. purchased from a license testing account
|
||||
@ -43,14 +43,14 @@ const (
|
||||
PurchaseTypeRewarded PurchaseType = 2 // i.e. from watching a video ad instead of paying
|
||||
)
|
||||
|
||||
type ConsumptionState int
|
||||
type ConsumptionState int //@enum:type
|
||||
|
||||
const (
|
||||
ConsumptionStateYetToBeConsumed ConsumptionState = 0
|
||||
ConsumptionStateConsumed ConsumptionState = 1
|
||||
)
|
||||
|
||||
type PurchaseState int
|
||||
type PurchaseState int //@enum:type
|
||||
|
||||
const (
|
||||
PurchaseStatePurchased PurchaseState = 0
|
||||
@ -58,7 +58,7 @@ const (
|
||||
PurchaseStatePending PurchaseState = 2
|
||||
)
|
||||
|
||||
type AcknowledgementState int
|
||||
type AcknowledgementState int //@enum:type
|
||||
|
||||
const (
|
||||
AcknowledgementStateYetToBeAcknowledged AcknowledgementState = 0
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type AppContext struct {
|
||||
app *Application
|
||||
inner context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
cancelled bool
|
||||
@ -22,8 +23,9 @@ type AppContext struct {
|
||||
ginContext *gin.Context
|
||||
}
|
||||
|
||||
func CreateAppContext(g *gin.Context, innerCtx context.Context, cancelFn context.CancelFunc) *AppContext {
|
||||
func CreateAppContext(app *Application, g *gin.Context, innerCtx context.Context, cancelFn context.CancelFunc) *AppContext {
|
||||
return &AppContext{
|
||||
app: app,
|
||||
inner: innerCtx,
|
||||
cancelFunc: cancelFn,
|
||||
cancelled: false,
|
||||
|
@ -248,7 +248,7 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an
|
||||
}
|
||||
|
||||
ictx, cancel := context.WithTimeout(context.Background(), app.Config.RequestTimeout)
|
||||
actx := CreateAppContext(g, ictx, cancel)
|
||||
actx := CreateAppContext(app, g, ictx, cancel)
|
||||
|
||||
authheader := g.GetHeader("Authorization")
|
||||
|
||||
@ -280,19 +280,19 @@ func (app *Application) getPermissions(ctx *AppContext, hdr string) (models.Perm
|
||||
|
||||
key := strings.TrimSpace(hdr[4:])
|
||||
|
||||
user, err := app.Database.Primary.GetUserByKey(ctx, key)
|
||||
tok, err := app.Database.Primary.GetKeyTokenByToken(ctx, key)
|
||||
if err != nil {
|
||||
return models.PermissionSet{}, err
|
||||
}
|
||||
|
||||
if user != nil && user.SendKey == key {
|
||||
return models.PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: models.PermKeyTypeUserSend}, nil
|
||||
if tok != nil {
|
||||
|
||||
err = app.Database.Primary.UpdateKeyTokenLastUsed(ctx, tok.KeyTokenID)
|
||||
if err != nil {
|
||||
return models.PermissionSet{}, err
|
||||
}
|
||||
if user != nil && user.ReadKey == key {
|
||||
return models.PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: models.PermKeyTypeUserRead}, nil
|
||||
}
|
||||
if user != nil && user.AdminKey == key {
|
||||
return models.PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: models.PermKeyTypeUserAdmin}, nil
|
||||
|
||||
return models.PermissionSet{Token: tok}, nil
|
||||
}
|
||||
|
||||
return models.NewEmptyPermissions(), nil
|
||||
@ -309,9 +309,8 @@ func (app *Application) GetOrCreateChannel(ctx *AppContext, userid models.UserID
|
||||
}
|
||||
|
||||
subscribeKey := app.GenerateRandomAuthKey()
|
||||
sendKey := app.GenerateRandomAuthKey()
|
||||
|
||||
newChan, err := app.Database.Primary.CreateChannel(ctx, userid, displayChanName, intChanName, subscribeKey, sendKey)
|
||||
newChan, err := app.Database.Primary.CreateChannel(ctx, userid, displayChanName, intChanName, subscribeKey)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
@ -4,94 +4,116 @@ import (
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
)
|
||||
|
||||
func (ac *AppContext) CheckPermissionUserRead(userid models.UserID) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == models.PermKeyTypeUserRead {
|
||||
return nil
|
||||
}
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == models.PermKeyTypeUserAdmin {
|
||||
if p.Token != nil && p.Token.IsUserRead(userid) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionRead() *ginresp.HTTPResponse {
|
||||
func (ac *AppContext) CheckPermissionSelfAllMessagesRead() *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && p.KeyType == models.PermKeyTypeUserRead {
|
||||
if p.Token != nil && p.Token.IsAllMessagesRead(p.Token.OwnerUserID) {
|
||||
return nil
|
||||
}
|
||||
if p.UserID != nil && p.KeyType == models.PermKeyTypeUserAdmin {
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionAllMessagesRead(userid models.UserID) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.Token != nil && p.Token.IsAllMessagesRead(userid) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionChanMessagesRead(channel models.Channel) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.Token != nil && p.Token.IsChannelMessagesRead(channel.ChannelID) {
|
||||
|
||||
if channel.OwnerUserID == p.Token.OwnerUserID {
|
||||
return nil // owned channel
|
||||
} else {
|
||||
sub, err := ac.app.Database.Primary.GetSubscriptionBySubscriber(ac, p.Token.OwnerUserID, channel.ChannelID)
|
||||
if err == sql.ErrNoRows {
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
if err != nil {
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err))
|
||||
}
|
||||
if !sub.Confirmed {
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionUserAdmin(userid models.UserID) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == models.PermKeyTypeUserAdmin {
|
||||
if p.Token != nil && p.Token.IsAdmin(userid) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionSend() *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && p.KeyType == models.PermKeyTypeUserSend {
|
||||
return nil
|
||||
func (ac *AppContext) CheckPermissionSend(channel models.Channel, key string) (*models.KeyToken, *ginresp.HTTPResponse) {
|
||||
|
||||
keytok, err := ac.app.Database.Primary.GetKeyTokenByToken(ac, key)
|
||||
if err != nil {
|
||||
return nil, langext.Ptr(ginresp.APIError(ac.ginContext, 500, apierr.DATABASE_ERROR, "Failed to query token", err))
|
||||
}
|
||||
if p.UserID != nil && p.KeyType == models.PermKeyTypeUserAdmin {
|
||||
return nil
|
||||
if keytok == nil {
|
||||
return nil, langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
if keytok.IsChannelMessagesSend(channel) {
|
||||
return keytok, nil
|
||||
}
|
||||
|
||||
return nil, langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionAny() *ginresp.HTTPResponse {
|
||||
func (ac *AppContext) CheckPermissionMessageRead(msg models.Message) bool {
|
||||
p := ac.permissions
|
||||
if p.KeyType == models.PermKeyTypeNone {
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionMessageReadDirect(msg models.Message) bool {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && msg.OwnerUserID == *p.UserID && p.KeyType == models.PermKeyTypeUserRead {
|
||||
return true
|
||||
}
|
||||
if p.UserID != nil && msg.OwnerUserID == *p.UserID && p.KeyType == models.PermKeyTypeUserAdmin {
|
||||
if p.Token != nil && p.Token.IsChannelMessagesRead(msg.ChannelID) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionAny() *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.Token == nil {
|
||||
return langext.Ptr(ginresp.APIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AppContext) GetPermissionUserID() *models.UserID {
|
||||
if ac.permissions.UserID == nil {
|
||||
if ac.permissions.Token == nil {
|
||||
return nil
|
||||
} else {
|
||||
return langext.Ptr(*ac.permissions.UserID)
|
||||
return langext.Ptr(ac.permissions.Token.OwnerUserID)
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserRead() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == models.PermKeyTypeUserRead || p.KeyType == models.PermKeyTypeUserAdmin
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserSend() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == models.PermKeyTypeUserSend || p.KeyType == models.PermKeyTypeUserAdmin
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserAdmin() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == models.PermKeyTypeUserAdmin
|
||||
func (ac *AppContext) GetPermissionKeyTokenID() *models.KeyTokenID {
|
||||
if ac.permissions.Token == nil {
|
||||
return nil
|
||||
} else {
|
||||
return langext.Ptr(ac.permissions.Token.KeyTokenID)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ type Channel struct {
|
||||
DisplayName string
|
||||
DescriptionName *string
|
||||
SubscribeKey string
|
||||
SendKey string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastSent *time.Time
|
||||
MessagesSent int
|
||||
@ -28,7 +27,6 @@ func (c Channel) JSON(includeKey bool) ChannelJSON {
|
||||
DisplayName: c.DisplayName,
|
||||
DescriptionName: c.DescriptionName,
|
||||
SubscribeKey: langext.Conditional(includeKey, langext.Ptr(c.SubscribeKey), nil),
|
||||
SendKey: langext.Conditional(includeKey, langext.Ptr(c.SendKey), nil),
|
||||
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
|
||||
MessagesSent: c.MessagesSent,
|
||||
@ -65,7 +63,6 @@ type ChannelJSON struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
DescriptionName *string `json:"description_name"`
|
||||
SubscribeKey *string `json:"subscribe_key"` // can be nil, depending on endpoint
|
||||
SendKey *string `json:"send_key"` // can be nil, depending on endpoint
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampLastSent *string `json:"timestamp_lastsent"`
|
||||
MessagesSent int `json:"messages_sent"`
|
||||
@ -98,8 +95,7 @@ func (c ChannelDB) Model() Channel {
|
||||
DisplayName: c.DisplayName,
|
||||
DescriptionName: c.DescriptionName,
|
||||
SubscribeKey: c.SubscribeKey,
|
||||
SendKey: c.SendKey,
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
TimestampCreated: timeFromMilli(c.TimestampCreated),
|
||||
TimestampLastSent: timeOptFromMilli(c.TimestampLastSent),
|
||||
MessagesSent: c.MessagesSent,
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClientType string
|
||||
type ClientType string //@enum:type
|
||||
|
||||
const (
|
||||
ClientTypeAndroid ClientType = "ANDROID"
|
||||
@ -62,7 +62,7 @@ func (c ClientDB) Model() Client {
|
||||
UserID: c.UserID,
|
||||
Type: c.Type,
|
||||
FCMToken: c.FCMToken,
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
TimestampCreated: timeFromMilli(c.TimestampCreated),
|
||||
AgentModel: c.AgentModel,
|
||||
AgentVersion: c.AgentVersion,
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type DeliveryStatus string
|
||||
type DeliveryStatus string //@enum:type
|
||||
|
||||
const (
|
||||
DeliveryStatusRetry DeliveryStatus = "RETRY"
|
||||
@ -79,7 +79,7 @@ func (d DeliveryDB) Model() Delivery {
|
||||
MessageID: d.MessageID,
|
||||
ReceiverUserID: d.ReceiverUserID,
|
||||
ReceiverClientID: d.ReceiverClientID,
|
||||
TimestampCreated: time.UnixMilli(d.TimestampCreated),
|
||||
TimestampCreated: timeFromMilli(d.TimestampCreated),
|
||||
TimestampFinalized: timeOptFromMilli(d.TimestampFinalized),
|
||||
Status: d.Status,
|
||||
RetryCount: d.RetryCount,
|
||||
|
242
scnserver/models/enums_gen.go
Normal file
242
scnserver/models/enums_gen.go
Normal file
@ -0,0 +1,242 @@
|
||||
// Code generated by permissions_gen.sh DO NOT EDIT.
|
||||
|
||||
package models
|
||||
|
||||
import "gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
|
||||
type Enum interface {
|
||||
Valid() bool
|
||||
ValuesAny() []any
|
||||
ValuesMeta() []EnumMetaValue
|
||||
VarName() string
|
||||
}
|
||||
|
||||
type StringEnum interface {
|
||||
Enum
|
||||
String() string
|
||||
}
|
||||
|
||||
type DescriptionEnum interface {
|
||||
Enum
|
||||
Description() string
|
||||
}
|
||||
|
||||
type EnumMetaValue struct {
|
||||
VarName string `json:"varName"`
|
||||
Value any `json:"value"`
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
// ================================ ClientType ================================
|
||||
//
|
||||
// File: client.go
|
||||
// StringEnum: true
|
||||
// DescrEnum: false
|
||||
//
|
||||
|
||||
var __ClientTypeValues = []ClientType{
|
||||
ClientTypeAndroid,
|
||||
ClientTypeIOS,
|
||||
}
|
||||
|
||||
var __ClientTypeVarnames = map[ClientType]string{
|
||||
ClientTypeAndroid: "ClientTypeAndroid",
|
||||
ClientTypeIOS: "ClientTypeIOS",
|
||||
}
|
||||
|
||||
func (e ClientType) Valid() bool {
|
||||
return langext.InArray(e, __ClientTypeValues)
|
||||
}
|
||||
|
||||
func (e ClientType) Values() []ClientType {
|
||||
return __ClientTypeValues
|
||||
}
|
||||
|
||||
func (e ClientType) ValuesAny() []any {
|
||||
return langext.ArrCastToAny(__ClientTypeValues)
|
||||
}
|
||||
|
||||
func (e ClientType) ValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "ClientTypeAndroid", Value: ClientTypeAndroid, Description: nil},
|
||||
EnumMetaValue{VarName: "ClientTypeIOS", Value: ClientTypeIOS, Description: nil},
|
||||
}
|
||||
}
|
||||
|
||||
func (e ClientType) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e ClientType) VarName() string {
|
||||
if d, ok := __ClientTypeVarnames[e]; ok {
|
||||
return d
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ParseClientType(vv string) (ClientType, bool) {
|
||||
for _, ev := range __ClientTypeValues {
|
||||
if string(ev) == vv {
|
||||
return ev, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func ClientTypeValues() []ClientType {
|
||||
return __ClientTypeValues
|
||||
}
|
||||
|
||||
func ClientTypeValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "ClientTypeAndroid", Value: ClientTypeAndroid, Description: nil},
|
||||
EnumMetaValue{VarName: "ClientTypeIOS", Value: ClientTypeIOS, Description: nil},
|
||||
}
|
||||
}
|
||||
|
||||
// ================================ DeliveryStatus ================================
|
||||
//
|
||||
// File: delivery.go
|
||||
// StringEnum: true
|
||||
// DescrEnum: false
|
||||
//
|
||||
|
||||
var __DeliveryStatusValues = []DeliveryStatus{
|
||||
DeliveryStatusRetry,
|
||||
DeliveryStatusSuccess,
|
||||
DeliveryStatusFailed,
|
||||
}
|
||||
|
||||
var __DeliveryStatusVarnames = map[DeliveryStatus]string{
|
||||
DeliveryStatusRetry: "DeliveryStatusRetry",
|
||||
DeliveryStatusSuccess: "DeliveryStatusSuccess",
|
||||
DeliveryStatusFailed: "DeliveryStatusFailed",
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) Valid() bool {
|
||||
return langext.InArray(e, __DeliveryStatusValues)
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) Values() []DeliveryStatus {
|
||||
return __DeliveryStatusValues
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) ValuesAny() []any {
|
||||
return langext.ArrCastToAny(__DeliveryStatusValues)
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) ValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "DeliveryStatusRetry", Value: DeliveryStatusRetry, Description: nil},
|
||||
EnumMetaValue{VarName: "DeliveryStatusSuccess", Value: DeliveryStatusSuccess, Description: nil},
|
||||
EnumMetaValue{VarName: "DeliveryStatusFailed", Value: DeliveryStatusFailed, Description: nil},
|
||||
}
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e DeliveryStatus) VarName() string {
|
||||
if d, ok := __DeliveryStatusVarnames[e]; ok {
|
||||
return d
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ParseDeliveryStatus(vv string) (DeliveryStatus, bool) {
|
||||
for _, ev := range __DeliveryStatusValues {
|
||||
if string(ev) == vv {
|
||||
return ev, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func DeliveryStatusValues() []DeliveryStatus {
|
||||
return __DeliveryStatusValues
|
||||
}
|
||||
|
||||
func DeliveryStatusValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "DeliveryStatusRetry", Value: DeliveryStatusRetry, Description: nil},
|
||||
EnumMetaValue{VarName: "DeliveryStatusSuccess", Value: DeliveryStatusSuccess, Description: nil},
|
||||
EnumMetaValue{VarName: "DeliveryStatusFailed", Value: DeliveryStatusFailed, Description: nil},
|
||||
}
|
||||
}
|
||||
|
||||
// ================================ TokenPerm ================================
|
||||
//
|
||||
// File: keytoken.go
|
||||
// StringEnum: true
|
||||
// DescrEnum: false
|
||||
//
|
||||
|
||||
var __TokenPermValues = []TokenPerm{
|
||||
PermAdmin,
|
||||
PermChannelRead,
|
||||
PermChannelSend,
|
||||
PermUserRead,
|
||||
}
|
||||
|
||||
var __TokenPermVarnames = map[TokenPerm]string{
|
||||
PermAdmin: "PermAdmin",
|
||||
PermChannelRead: "PermChannelRead",
|
||||
PermChannelSend: "PermChannelSend",
|
||||
PermUserRead: "PermUserRead",
|
||||
}
|
||||
|
||||
func (e TokenPerm) Valid() bool {
|
||||
return langext.InArray(e, __TokenPermValues)
|
||||
}
|
||||
|
||||
func (e TokenPerm) Values() []TokenPerm {
|
||||
return __TokenPermValues
|
||||
}
|
||||
|
||||
func (e TokenPerm) ValuesAny() []any {
|
||||
return langext.ArrCastToAny(__TokenPermValues)
|
||||
}
|
||||
|
||||
func (e TokenPerm) ValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "PermAdmin", Value: PermAdmin, Description: nil},
|
||||
EnumMetaValue{VarName: "PermChannelRead", Value: PermChannelRead, Description: nil},
|
||||
EnumMetaValue{VarName: "PermChannelSend", Value: PermChannelSend, Description: nil},
|
||||
EnumMetaValue{VarName: "PermUserRead", Value: PermUserRead, Description: nil},
|
||||
}
|
||||
}
|
||||
|
||||
func (e TokenPerm) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e TokenPerm) VarName() string {
|
||||
if d, ok := __TokenPermVarnames[e]; ok {
|
||||
return d
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ParseTokenPerm(vv string) (TokenPerm, bool) {
|
||||
for _, ev := range __TokenPermValues {
|
||||
if string(ev) == vv {
|
||||
return ev, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func TokenPermValues() []TokenPerm {
|
||||
return __TokenPermValues
|
||||
}
|
||||
|
||||
func TokenPermValuesMeta() []EnumMetaValue {
|
||||
return []EnumMetaValue{
|
||||
EnumMetaValue{VarName: "PermAdmin", Value: PermAdmin, Description: nil},
|
||||
EnumMetaValue{VarName: "PermChannelRead", Value: PermChannelRead, Description: nil},
|
||||
EnumMetaValue{VarName: "PermChannelSend", Value: PermChannelSend, Description: nil},
|
||||
EnumMetaValue{VarName: "PermUserRead", Value: PermUserRead, Description: nil},
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ const (
|
||||
prefixSubscriptionID = "SUB"
|
||||
prefixClientID = "CLN"
|
||||
prefixRequestID = "REQ"
|
||||
prefixKeyTokenID = "TOK"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -50,6 +51,7 @@ var (
|
||||
regexSubscriptionID = generateRegex(prefixSubscriptionID)
|
||||
regexClientID = generateRegex(prefixClientID)
|
||||
regexRequestID = generateRegex(prefixRequestID)
|
||||
regexKeyTokenID = generateRegex(prefixKeyTokenID)
|
||||
)
|
||||
|
||||
func generateRegex(prefix string) rext.Regex {
|
||||
@ -375,3 +377,35 @@ func (id RequestID) CheckString() string {
|
||||
func (id RequestID) Regex() rext.Regex {
|
||||
return regexRequestID
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type KeyTokenID string
|
||||
|
||||
func NewKeyTokenID() KeyTokenID {
|
||||
return KeyTokenID(generateID(prefixKeyTokenID))
|
||||
}
|
||||
|
||||
func (id KeyTokenID) Valid() error {
|
||||
return validateID(prefixKeyTokenID, string(id))
|
||||
}
|
||||
|
||||
func (id KeyTokenID) String() string {
|
||||
return string(id)
|
||||
}
|
||||
|
||||
func (id KeyTokenID) Prefix() string {
|
||||
return prefixKeyTokenID
|
||||
}
|
||||
|
||||
func (id KeyTokenID) Raw() string {
|
||||
return getRawData(prefixKeyTokenID, string(id))
|
||||
}
|
||||
|
||||
func (id KeyTokenID) CheckString() string {
|
||||
return getCheckString(prefixKeyTokenID, string(id))
|
||||
}
|
||||
|
||||
func (id KeyTokenID) Regex() rext.Regex {
|
||||
return regexKeyTokenID
|
||||
}
|
||||
|
160
scnserver/models/keytoken.go
Normal file
160
scnserver/models/keytoken.go
Normal file
@ -0,0 +1,160 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenPerm string //@enum:type
|
||||
|
||||
const (
|
||||
PermAdmin TokenPerm = "A"
|
||||
PermChannelRead TokenPerm = "CR"
|
||||
PermChannelSend TokenPerm = "CS"
|
||||
PermUserRead TokenPerm = "UR"
|
||||
)
|
||||
|
||||
type TokenPermissionList []TokenPerm
|
||||
|
||||
func (e TokenPermissionList) Any(p ...TokenPerm) bool {
|
||||
for _, v1 := range e {
|
||||
for _, v2 := range p {
|
||||
if v1 == v2 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e TokenPermissionList) String() string {
|
||||
return strings.Join(langext.ArrMap(e, func(v TokenPerm) string { return string(v) }), ";")
|
||||
}
|
||||
|
||||
func ParseTokenPermissionList(input string) TokenPermissionList {
|
||||
r := make([]TokenPerm, 0, len(input))
|
||||
for _, v := range strings.Split(input, ";") {
|
||||
if vv, ok := ParseTokenPerm(v); ok {
|
||||
r = append(r, vv)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type KeyToken struct {
|
||||
KeyTokenID KeyTokenID
|
||||
Name string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastUsed *time.Time
|
||||
OwnerUserID UserID
|
||||
AllChannels bool
|
||||
Channels []ChannelID // can also be owned by other user (needs active subscription)
|
||||
Token string
|
||||
Permissions TokenPermissionList
|
||||
MessagesSent int
|
||||
}
|
||||
|
||||
func (k KeyToken) IsUserRead(uid UserID) bool {
|
||||
return k.OwnerUserID == uid && k.Permissions.Any(PermAdmin, PermUserRead)
|
||||
}
|
||||
|
||||
func (k KeyToken) IsAllMessagesRead(uid UserID) bool {
|
||||
return k.OwnerUserID == uid && k.AllChannels == true && k.Permissions.Any(PermAdmin, PermChannelRead)
|
||||
}
|
||||
|
||||
func (k KeyToken) IsChannelMessagesRead(cid ChannelID) bool {
|
||||
return (k.AllChannels == true || langext.InArray(cid, k.Channels)) && k.Permissions.Any(PermAdmin, PermChannelRead)
|
||||
}
|
||||
|
||||
func (k KeyToken) IsAdmin(uid UserID) bool {
|
||||
return k.OwnerUserID == uid && k.Permissions.Any(PermAdmin)
|
||||
}
|
||||
|
||||
func (k KeyToken) IsChannelMessagesSend(c Channel) bool {
|
||||
return (k.AllChannels == true || langext.InArray(c.ChannelID, k.Channels)) && k.OwnerUserID == c.OwnerUserID && k.Permissions.Any(PermAdmin, PermChannelSend)
|
||||
}
|
||||
|
||||
func (k KeyToken) JSON() KeyTokenJSON {
|
||||
return KeyTokenJSON{
|
||||
KeyTokenID: k.KeyTokenID,
|
||||
Name: k.Name,
|
||||
TimestampCreated: k.TimestampCreated,
|
||||
TimestampLastUsed: k.TimestampLastUsed,
|
||||
OwnerUserID: k.OwnerUserID,
|
||||
AllChannels: k.AllChannels,
|
||||
Channels: k.Channels,
|
||||
Permissions: k.Permissions.String(),
|
||||
MessagesSent: k.MessagesSent,
|
||||
}
|
||||
}
|
||||
|
||||
type KeyTokenJSON struct {
|
||||
KeyTokenID KeyTokenID `json:"keytoken_id"`
|
||||
Name string `json:"name"`
|
||||
TimestampCreated time.Time `json:"timestamp_created"`
|
||||
TimestampLastUsed *time.Time `json:"timestamp_lastused"`
|
||||
OwnerUserID UserID `json:"owner_user_id"`
|
||||
AllChannels bool `json:"all_channels"`
|
||||
Channels []ChannelID `json:"channels"`
|
||||
Permissions string `json:"permissions"`
|
||||
MessagesSent int `json:"messages_sent"`
|
||||
}
|
||||
|
||||
type KeyTokenWithTokenJSON struct {
|
||||
KeyTokenJSON
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (j KeyTokenJSON) WithToken(tok string) any {
|
||||
return KeyTokenWithTokenJSON{
|
||||
KeyTokenJSON: j,
|
||||
Token: tok,
|
||||
}
|
||||
}
|
||||
|
||||
type KeyTokenDB struct {
|
||||
KeyTokenID KeyTokenID `db:"keytoken_id"`
|
||||
Name string `db:"name"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
TimestampLastUsed *int64 `db:"timestamp_lastused"`
|
||||
OwnerUserID UserID `db:"owner_user_id"`
|
||||
AllChannels bool `db:"all_channels"`
|
||||
Channels string `db:"channels"`
|
||||
Token string `db:"token"`
|
||||
Permissions string `db:"permissions"`
|
||||
MessagesSent int `db:"messages_sent"`
|
||||
}
|
||||
|
||||
func (k KeyTokenDB) Model() KeyToken {
|
||||
return KeyToken{
|
||||
KeyTokenID: k.KeyTokenID,
|
||||
Name: k.Name,
|
||||
TimestampCreated: timeFromMilli(k.TimestampCreated),
|
||||
TimestampLastUsed: timeOptFromMilli(k.TimestampLastUsed),
|
||||
OwnerUserID: k.OwnerUserID,
|
||||
AllChannels: k.AllChannels,
|
||||
Channels: langext.ArrMap(strings.Split(k.Channels, ";"), func(v string) ChannelID { return ChannelID(v) }),
|
||||
Token: k.Token,
|
||||
Permissions: ParseTokenPermissionList(k.Permissions),
|
||||
MessagesSent: k.MessagesSent,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeKeyToken(r *sqlx.Rows) (KeyToken, error) {
|
||||
data, err := sq.ScanSingle[KeyTokenDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return KeyToken{}, err
|
||||
}
|
||||
return data.Model(), nil
|
||||
}
|
||||
|
||||
func DecodeKeyTokens(r *sqlx.Rows) ([]KeyToken, error) {
|
||||
data, err := sq.ScanAll[KeyTokenDB](r, sq.SModeFast, sq.Safe, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return langext.ArrMap(data, func(v KeyTokenDB) KeyToken { return v.Model() }), nil
|
||||
}
|
@ -135,7 +135,7 @@ func (m MessageDB) Model() Message {
|
||||
ChannelID: m.ChannelID,
|
||||
SenderName: m.SenderName,
|
||||
SenderIP: m.SenderIP,
|
||||
TimestampReal: time.UnixMilli(m.TimestampReal),
|
||||
TimestampReal: timeFromMilli(m.TimestampReal),
|
||||
TimestampClient: timeOptFromMilli(m.TimestampClient),
|
||||
Title: m.Title,
|
||||
Content: m.Content,
|
||||
|
@ -1,22 +1,11 @@
|
||||
package models
|
||||
|
||||
type PermKeyType string
|
||||
|
||||
const (
|
||||
PermKeyTypeNone PermKeyType = "NONE" // (nothing)
|
||||
PermKeyTypeUserSend PermKeyType = "USER_SEND" // send-messages
|
||||
PermKeyTypeUserRead PermKeyType = "USER_READ" // send-messages, list-messages, read-user
|
||||
PermKeyTypeUserAdmin PermKeyType = "USER_ADMIN" // send-messages, list-messages, read-user, delete-messages, update-user
|
||||
)
|
||||
|
||||
type PermissionSet struct {
|
||||
UserID *UserID
|
||||
KeyType PermKeyType
|
||||
Token *KeyToken // KeyToken.Permissions
|
||||
}
|
||||
|
||||
func NewEmptyPermissions() PermissionSet {
|
||||
return PermissionSet{
|
||||
UserID: nil,
|
||||
KeyType: PermKeyTypeNone,
|
||||
Token: nil,
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ type RequestLog struct {
|
||||
RequestBodySize int64
|
||||
RequestContentType string
|
||||
RemoteIP string
|
||||
TokenID *KeyTokenID
|
||||
UserID *UserID
|
||||
Permissions *string
|
||||
ResponseStatuscode *int64
|
||||
@ -44,6 +45,7 @@ func (c RequestLog) JSON() RequestLogJSON {
|
||||
RequestBodySize: c.RequestBodySize,
|
||||
RequestContentType: c.RequestContentType,
|
||||
RemoteIP: c.RemoteIP,
|
||||
TokenID: c.TokenID,
|
||||
UserID: c.UserID,
|
||||
Permissions: c.Permissions,
|
||||
ResponseStatuscode: c.ResponseStatuscode,
|
||||
@ -71,6 +73,7 @@ func (c RequestLog) DB() RequestLogDB {
|
||||
RequestBodySize: c.RequestBodySize,
|
||||
RequestContentType: c.RequestContentType,
|
||||
RemoteIP: c.RemoteIP,
|
||||
TokenID: c.TokenID,
|
||||
UserID: c.UserID,
|
||||
Permissions: c.Permissions,
|
||||
ResponseStatuscode: c.ResponseStatuscode,
|
||||
@ -97,6 +100,7 @@ type RequestLogJSON struct {
|
||||
RequestBodySize int64 `json:"request_body_size"`
|
||||
RequestContentType string `json:"request_content_type"`
|
||||
RemoteIP string `json:"remote_ip"`
|
||||
TokenID *KeyTokenID `json:"token_id"`
|
||||
UserID *UserID `json:"userid"`
|
||||
Permissions *string `json:"permissions"`
|
||||
ResponseStatuscode *int64 `json:"response_statuscode"`
|
||||
@ -122,6 +126,7 @@ type RequestLogDB struct {
|
||||
RequestBodySize int64 `db:"request_body_size"`
|
||||
RequestContentType string `db:"request_content_type"`
|
||||
RemoteIP string `db:"remote_ip"`
|
||||
TokenID *KeyTokenID `db:"token_id"`
|
||||
UserID *UserID `db:"userid"`
|
||||
Permissions *string `db:"permissions"`
|
||||
ResponseStatuscode *int64 `db:"response_statuscode"`
|
||||
@ -158,9 +163,9 @@ func (c RequestLogDB) Model() RequestLog {
|
||||
Panicked: c.Panicked != 0,
|
||||
PanicStr: c.PanicStr,
|
||||
ProcessingTime: timeext.FromMilliseconds(c.ProcessingTime),
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
TimestampStart: time.UnixMilli(c.TimestampStart),
|
||||
TimestampFinish: time.UnixMilli(c.TimestampFinish),
|
||||
TimestampCreated: timeFromMilli(c.TimestampCreated),
|
||||
TimestampStart: timeFromMilli(c.TimestampStart),
|
||||
TimestampFinish: timeFromMilli(c.TimestampFinish),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// [!] subscriptions are read-access to channels,
|
||||
//
|
||||
// The set of subscriptions specifies which messages the ListMessages() API call returns
|
||||
// also single messages/channels that are subscribed can be queries
|
||||
//
|
||||
// (use keytokens for write-access)
|
||||
|
||||
type Subscription struct {
|
||||
SubscriptionID SubscriptionID
|
||||
SubscriberUserID UserID
|
||||
@ -56,7 +63,7 @@ func (s SubscriptionDB) Model() Subscription {
|
||||
ChannelOwnerUserID: s.ChannelOwnerUserID,
|
||||
ChannelID: s.ChannelID,
|
||||
ChannelInternalName: s.ChannelInternalName,
|
||||
TimestampCreated: time.UnixMilli(s.TimestampCreated),
|
||||
TimestampCreated: timeFromMilli(s.TimestampCreated),
|
||||
Confirmed: s.Confirmed != 0,
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,6 @@ import (
|
||||
type User struct {
|
||||
UserID UserID
|
||||
Username *string
|
||||
SendKey string
|
||||
ReadKey string
|
||||
AdminKey string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastRead *time.Time
|
||||
TimestampLastSent *time.Time
|
||||
@ -28,9 +25,6 @@ func (u User) JSON() UserJSON {
|
||||
return UserJSON{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
ReadKey: u.ReadKey,
|
||||
SendKey: u.SendKey,
|
||||
AdminKey: u.AdminKey,
|
||||
TimestampCreated: u.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampLastRead: timeOptFmt(u.TimestampLastRead, time.RFC3339Nano),
|
||||
TimestampLastSent: timeOptFmt(u.TimestampLastSent, time.RFC3339Nano),
|
||||
@ -43,10 +37,13 @@ func (u User) JSON() UserJSON {
|
||||
}
|
||||
}
|
||||
|
||||
func (u User) JSONWithClients(clients []Client) UserJSONWithClients {
|
||||
return UserJSONWithClients{
|
||||
func (u User) JSONWithClients(clients []Client, ak string, sk string, rk string) UserJSONWithClientsAndKeys {
|
||||
return UserJSONWithClientsAndKeys{
|
||||
UserJSON: u.JSON(),
|
||||
Clients: langext.ArrMap(clients, func(v Client) ClientJSON { return v.JSON() }),
|
||||
SendKey: sk,
|
||||
ReadKey: rk,
|
||||
AdminKey: ak,
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,9 +111,6 @@ func (u User) MaxTimestampDiffHours() int {
|
||||
type UserJSON struct {
|
||||
UserID UserID `json:"user_id"`
|
||||
Username *string `json:"username"`
|
||||
ReadKey string `json:"read_key"`
|
||||
SendKey string `json:"send_key"`
|
||||
AdminKey string `json:"admin_key"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampLastRead *string `json:"timestamp_lastread"`
|
||||
TimestampLastSent *string `json:"timestamp_lastsent"`
|
||||
@ -128,17 +122,17 @@ type UserJSON struct {
|
||||
DefaultChannel string `json:"default_channel"`
|
||||
}
|
||||
|
||||
type UserJSONWithClients struct {
|
||||
type UserJSONWithClientsAndKeys struct {
|
||||
UserJSON
|
||||
Clients []ClientJSON `json:"clients"`
|
||||
SendKey string `json:"send_key"`
|
||||
ReadKey string `json:"read_key"`
|
||||
AdminKey string `json:"admin_key"`
|
||||
}
|
||||
|
||||
type UserDB struct {
|
||||
UserID UserID `db:"user_id"`
|
||||
Username *string `db:"username"`
|
||||
SendKey string `db:"send_key"`
|
||||
ReadKey string `db:"read_key"`
|
||||
AdminKey string `db:"admin_key"`
|
||||
TimestampCreated int64 `db:"timestamp_created"`
|
||||
TimestampLastRead *int64 `db:"timestamp_lastread"`
|
||||
TimestampLastSent *int64 `db:"timestamp_lastsent"`
|
||||
@ -153,10 +147,7 @@ func (u UserDB) Model() User {
|
||||
return User{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
SendKey: u.SendKey,
|
||||
ReadKey: u.ReadKey,
|
||||
AdminKey: u.AdminKey,
|
||||
TimestampCreated: time.UnixMilli(u.TimestampCreated),
|
||||
TimestampCreated: timeFromMilli(u.TimestampCreated),
|
||||
TimestampLastRead: timeOptFromMilli(u.TimestampLastRead),
|
||||
TimestampLastSent: timeOptFromMilli(u.TimestampLastSent),
|
||||
MessagesSent: u.MessagesSent,
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go run ../_gen/enum-generate.go -- enums_gen.go
|
||||
|
||||
func timeOptFmt(t *time.Time, fmt string) *string {
|
||||
if t == nil {
|
||||
return nil
|
||||
@ -19,3 +21,7 @@ func timeOptFromMilli(millis *int64) *time.Time {
|
||||
}
|
||||
return langext.Ptr(time.UnixMilli(*millis))
|
||||
}
|
||||
|
||||
func timeFromMilli(millis int64) time.Time {
|
||||
return time.UnixMilli(millis)
|
||||
}
|
||||
|
@ -17,12 +17,6 @@
|
||||
],
|
||||
"summary": "Send a new message",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "qhnUbKcLgp6tg",
|
||||
"name": "chan_key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "test",
|
||||
@ -35,6 +29,12 @@
|
||||
"name": "content",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "db8b0e6a-a08c-4646",
|
||||
@ -76,12 +76,6 @@
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "user_key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": " ",
|
||||
"name": "post_body",
|
||||
@ -90,12 +84,6 @@
|
||||
"$ref": "#/definitions/handler.SendMessage.combined"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "qhnUbKcLgp6tg",
|
||||
"name": "chan_key",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "test",
|
||||
@ -108,6 +96,12 @@
|
||||
"name": "content",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "key",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "db8b0e6a-a08c-4646",
|
||||
@ -148,12 +142,6 @@
|
||||
"example": "7725",
|
||||
"name": "user_id",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "user_key",
|
||||
"in": "formData"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -1034,7 +1022,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserJSONWithClients"
|
||||
"$ref": "#/definitions/models.UserJSONWithClientsAndKeys"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@ -1052,6 +1040,282 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/users/:uid/keys": {
|
||||
"get": {
|
||||
"description": "The request must be done with an ADMIN key, the returned keys are without their token.",
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "List keys of the user",
|
||||
"operationId": "api-tokenkeys-list",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "UserID",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ListUserKeys.response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "user is not authorized / has missing permissions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "message not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "Create a new key",
|
||||
"operationId": "api-tokenkeys-create",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "UserID",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": " ",
|
||||
"name": "post_body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.CreateUserKey.body"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.KeyTokenJSON"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "user is not authorized / has missing permissions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "message not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/users/:uid/keys/:kid": {
|
||||
"get": {
|
||||
"description": "The request must be done with an ADMIN key, the returned key does not include its token.",
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "Get a single key",
|
||||
"operationId": "api-tokenkeys-get",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "UserID",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "TokenKeyID",
|
||||
"name": "kid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.KeyTokenJSON"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "user is not authorized / has missing permissions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "message not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Cannot be used to delete the key used in the request itself",
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "Delete a key",
|
||||
"operationId": "api-tokenkeys-delete",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "UserID",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "TokenKeyID",
|
||||
"name": "kid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.KeyTokenJSON"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "user is not authorized / has missing permissions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "message not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"API-v2"
|
||||
],
|
||||
"summary": "Update a key",
|
||||
"operationId": "api-tokenkeys-update",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "UserID",
|
||||
"name": "uid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "TokenKeyID",
|
||||
"name": "kid",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.KeyTokenJSON"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "supplied values/parameters cannot be parsed / are invalid",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "user is not authorized / has missing permissions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "message not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ginresp.apiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/users/{uid}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2081,12 +2345,6 @@
|
||||
],
|
||||
"summary": "Send a new message",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"example": "qhnUbKcLgp6tg",
|
||||
"name": "chan_key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "test",
|
||||
@ -2099,6 +2357,12 @@
|
||||
"name": "content",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "db8b0e6a-a08c-4646",
|
||||
@ -2140,12 +2404,6 @@
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "user_key",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": " ",
|
||||
"name": "post_body",
|
||||
@ -2154,12 +2412,6 @@
|
||||
"$ref": "#/definitions/handler.SendMessage.combined"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "qhnUbKcLgp6tg",
|
||||
"name": "chan_key",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "test",
|
||||
@ -2172,6 +2424,12 @@
|
||||
"name": "content",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "key",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "db8b0e6a-a08c-4646",
|
||||
@ -2212,12 +2470,6 @@
|
||||
"example": "7725",
|
||||
"name": "user_id",
|
||||
"in": "formData"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm",
|
||||
"name": "user_key",
|
||||
"in": "formData"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -2370,6 +2622,97 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"apierr.APIError": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
-1,
|
||||
0,
|
||||
1101,
|
||||
1102,
|
||||
1103,
|
||||
1104,
|
||||
1105,
|
||||
1106,
|
||||
1121,
|
||||
1151,
|
||||
1152,
|
||||
1153,
|
||||
1161,
|
||||
1171,
|
||||
1201,
|
||||
1202,
|
||||
1203,
|
||||
1204,
|
||||
1205,
|
||||
1206,
|
||||
1207,
|
||||
1208,
|
||||
1251,
|
||||
1301,
|
||||
1302,
|
||||
1303,
|
||||
1304,
|
||||
1305,
|
||||
1306,
|
||||
1307,
|
||||
1311,
|
||||
1401,
|
||||
1501,
|
||||
1511,
|
||||
2101,
|
||||
3001,
|
||||
3002,
|
||||
9901,
|
||||
9902,
|
||||
9903,
|
||||
9904,
|
||||
9905
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"UNDEFINED",
|
||||
"NO_ERROR",
|
||||
"MISSING_UID",
|
||||
"MISSING_TOK",
|
||||
"MISSING_TITLE",
|
||||
"INVALID_PRIO",
|
||||
"REQ_METHOD",
|
||||
"INVALID_CLIENTTYPE",
|
||||
"PAGETOKEN_ERROR",
|
||||
"BINDFAIL_QUERY_PARAM",
|
||||
"BINDFAIL_BODY_PARAM",
|
||||
"BINDFAIL_URI_PARAM",
|
||||
"INVALID_BODY_PARAM",
|
||||
"INVALID_ENUM_VALUE",
|
||||
"NO_TITLE",
|
||||
"TITLE_TOO_LONG",
|
||||
"CONTENT_TOO_LONG",
|
||||
"USR_MSG_ID_TOO_LONG",
|
||||
"TIMESTAMP_OUT_OF_RANGE",
|
||||
"SENDERNAME_TOO_LONG",
|
||||
"CHANNEL_TOO_LONG",
|
||||
"CHANNEL_DESCRIPTION_TOO_LONG",
|
||||
"CHANNEL_NAME_WOULD_CHANGE",
|
||||
"USER_NOT_FOUND",
|
||||
"CLIENT_NOT_FOUND",
|
||||
"CHANNEL_NOT_FOUND",
|
||||
"SUBSCRIPTION_NOT_FOUND",
|
||||
"MESSAGE_NOT_FOUND",
|
||||
"SUBSCRIPTION_USER_MISMATCH",
|
||||
"KEY_NOT_FOUND",
|
||||
"USER_AUTH_FAILED",
|
||||
"NO_DEVICE_LINKED",
|
||||
"CHANNEL_ALREADY_EXISTS",
|
||||
"CANNOT_SELFDELETE_KEY",
|
||||
"QUOTA_REACHED",
|
||||
"FAILED_VERIFY_PRO_TOKEN",
|
||||
"INVALID_PRO_TOKEN",
|
||||
"FIREBASE_COM_FAILED",
|
||||
"FIREBASE_COM_ERRORED",
|
||||
"INTERNAL_EXCEPTION",
|
||||
"PANIC",
|
||||
"NOT_IMPLEMENTED"
|
||||
]
|
||||
},
|
||||
"ginresp.apiError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2492,6 +2835,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.CreateUserKey.body": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"all_channels",
|
||||
"channels",
|
||||
"name",
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"all_channels": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"channels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.DatabaseTest.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2630,6 +2999,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ListUserKeys.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tokens": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.KeyTokenJSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ListUserSubscriptions.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2690,10 +3070,6 @@
|
||||
"handler.SendMessage.combined": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chan_key": {
|
||||
"type": "string",
|
||||
"example": "qhnUbKcLgp6tg"
|
||||
},
|
||||
"channel": {
|
||||
"type": "string",
|
||||
"example": "test"
|
||||
@ -2702,6 +3078,10 @@
|
||||
"type": "string",
|
||||
"example": "This is a message"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm"
|
||||
},
|
||||
"msg_id": {
|
||||
"type": "string",
|
||||
"example": "db8b0e6a-a08c-4646"
|
||||
@ -2730,10 +3110,6 @@
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"example": "7725"
|
||||
},
|
||||
"user_key": {
|
||||
"type": "string",
|
||||
"example": "P3TNH8mvv14fm"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2744,7 +3120,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/apierr.APIError"
|
||||
},
|
||||
"is_pro": {
|
||||
"type": "boolean"
|
||||
@ -2779,7 +3155,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/apierr.APIError"
|
||||
},
|
||||
"is_pro": {
|
||||
"type": "boolean"
|
||||
@ -2936,10 +3312,6 @@
|
||||
"owner_user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"send_key": {
|
||||
"description": "can be nil, depending on endpoint",
|
||||
"type": "string"
|
||||
},
|
||||
"subscribe_key": {
|
||||
"description": "can be nil, depending on endpoint",
|
||||
"type": "string"
|
||||
@ -2974,13 +3346,24 @@
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/models.ClientType"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ClientType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ANDROID",
|
||||
"IOS"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ClientTypeAndroid",
|
||||
"ClientTypeIOS"
|
||||
]
|
||||
},
|
||||
"models.CompatMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -3007,6 +3390,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.KeyTokenJSON": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_channels": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"channels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"keytoken_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"messages_sent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner_user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp_created": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp_lastused": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.MessageJSON": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -3080,9 +3498,6 @@
|
||||
"models.UserJSON": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admin_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"default_channel": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3101,12 +3516,6 @@
|
||||
"quota_used": {
|
||||
"type": "integer"
|
||||
},
|
||||
"read_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"send_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp_created": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -3124,7 +3533,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserJSONWithClients": {
|
||||
"models.UserJSONWithClientsAndKeys": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admin_key": {
|
||||
|
@ -1,5 +1,93 @@
|
||||
basePath: /
|
||||
definitions:
|
||||
apierr.APIError:
|
||||
enum:
|
||||
- -1
|
||||
- 0
|
||||
- 1101
|
||||
- 1102
|
||||
- 1103
|
||||
- 1104
|
||||
- 1105
|
||||
- 1106
|
||||
- 1121
|
||||
- 1151
|
||||
- 1152
|
||||
- 1153
|
||||
- 1161
|
||||
- 1171
|
||||
- 1201
|
||||
- 1202
|
||||
- 1203
|
||||
- 1204
|
||||
- 1205
|
||||
- 1206
|
||||
- 1207
|
||||
- 1208
|
||||
- 1251
|
||||
- 1301
|
||||
- 1302
|
||||
- 1303
|
||||
- 1304
|
||||
- 1305
|
||||
- 1306
|
||||
- 1307
|
||||
- 1311
|
||||
- 1401
|
||||
- 1501
|
||||
- 1511
|
||||
- 2101
|
||||
- 3001
|
||||
- 3002
|
||||
- 9901
|
||||
- 9902
|
||||
- 9903
|
||||
- 9904
|
||||
- 9905
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- UNDEFINED
|
||||
- NO_ERROR
|
||||
- MISSING_UID
|
||||
- MISSING_TOK
|
||||
- MISSING_TITLE
|
||||
- INVALID_PRIO
|
||||
- REQ_METHOD
|
||||
- INVALID_CLIENTTYPE
|
||||
- PAGETOKEN_ERROR
|
||||
- BINDFAIL_QUERY_PARAM
|
||||
- BINDFAIL_BODY_PARAM
|
||||
- BINDFAIL_URI_PARAM
|
||||
- INVALID_BODY_PARAM
|
||||
- INVALID_ENUM_VALUE
|
||||
- NO_TITLE
|
||||
- TITLE_TOO_LONG
|
||||
- CONTENT_TOO_LONG
|
||||
- USR_MSG_ID_TOO_LONG
|
||||
- TIMESTAMP_OUT_OF_RANGE
|
||||
- SENDERNAME_TOO_LONG
|
||||
- CHANNEL_TOO_LONG
|
||||
- CHANNEL_DESCRIPTION_TOO_LONG
|
||||
- CHANNEL_NAME_WOULD_CHANGE
|
||||
- USER_NOT_FOUND
|
||||
- CLIENT_NOT_FOUND
|
||||
- CHANNEL_NOT_FOUND
|
||||
- SUBSCRIPTION_NOT_FOUND
|
||||
- MESSAGE_NOT_FOUND
|
||||
- SUBSCRIPTION_USER_MISMATCH
|
||||
- KEY_NOT_FOUND
|
||||
- USER_AUTH_FAILED
|
||||
- NO_DEVICE_LINKED
|
||||
- CHANNEL_ALREADY_EXISTS
|
||||
- CANNOT_SELFDELETE_KEY
|
||||
- QUOTA_REACHED
|
||||
- FAILED_VERIFY_PRO_TOKEN
|
||||
- INVALID_PRO_TOKEN
|
||||
- FIREBASE_COM_FAILED
|
||||
- FIREBASE_COM_ERRORED
|
||||
- INTERNAL_EXCEPTION
|
||||
- PANIC
|
||||
- NOT_IMPLEMENTED
|
||||
ginresp.apiError:
|
||||
properties:
|
||||
errhighlight:
|
||||
@ -80,6 +168,24 @@ definitions:
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
handler.CreateUserKey.body:
|
||||
properties:
|
||||
all_channels:
|
||||
type: boolean
|
||||
channels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
permissions:
|
||||
type: string
|
||||
required:
|
||||
- all_channels
|
||||
- channels
|
||||
- name
|
||||
- permissions
|
||||
type: object
|
||||
handler.DatabaseTest.response:
|
||||
properties:
|
||||
libVersion:
|
||||
@ -169,6 +275,13 @@ definitions:
|
||||
page_size:
|
||||
type: integer
|
||||
type: object
|
||||
handler.ListUserKeys.response:
|
||||
properties:
|
||||
tokens:
|
||||
items:
|
||||
$ref: '#/definitions/models.KeyTokenJSON'
|
||||
type: array
|
||||
type: object
|
||||
handler.ListUserSubscriptions.response:
|
||||
properties:
|
||||
subscriptions:
|
||||
@ -208,15 +321,15 @@ definitions:
|
||||
type: object
|
||||
handler.SendMessage.combined:
|
||||
properties:
|
||||
chan_key:
|
||||
example: qhnUbKcLgp6tg
|
||||
type: string
|
||||
channel:
|
||||
example: test
|
||||
type: string
|
||||
content:
|
||||
example: This is a message
|
||||
type: string
|
||||
key:
|
||||
example: P3TNH8mvv14fm
|
||||
type: string
|
||||
msg_id:
|
||||
example: db8b0e6a-a08c-4646
|
||||
type: string
|
||||
@ -239,16 +352,13 @@ definitions:
|
||||
user_id:
|
||||
example: "7725"
|
||||
type: string
|
||||
user_key:
|
||||
example: P3TNH8mvv14fm
|
||||
type: string
|
||||
type: object
|
||||
handler.SendMessage.response:
|
||||
properties:
|
||||
errhighlight:
|
||||
type: integer
|
||||
error:
|
||||
type: integer
|
||||
$ref: '#/definitions/apierr.APIError'
|
||||
is_pro:
|
||||
type: boolean
|
||||
message:
|
||||
@ -271,7 +381,7 @@ definitions:
|
||||
errhighlight:
|
||||
type: integer
|
||||
error:
|
||||
type: integer
|
||||
$ref: '#/definitions/apierr.APIError'
|
||||
is_pro:
|
||||
type: boolean
|
||||
message:
|
||||
@ -373,9 +483,6 @@ definitions:
|
||||
type: integer
|
||||
owner_user_id:
|
||||
type: string
|
||||
send_key:
|
||||
description: can be nil, depending on endpoint
|
||||
type: string
|
||||
subscribe_key:
|
||||
description: can be nil, depending on endpoint
|
||||
type: string
|
||||
@ -399,10 +506,18 @@ definitions:
|
||||
timestamp_created:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
$ref: '#/definitions/models.ClientType'
|
||||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
models.ClientType:
|
||||
enum:
|
||||
- ANDROID
|
||||
- IOS
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- ClientTypeAndroid
|
||||
- ClientTypeIOS
|
||||
models.CompatMessage:
|
||||
properties:
|
||||
body:
|
||||
@ -420,6 +535,29 @@ definitions:
|
||||
usr_msg_id:
|
||||
type: string
|
||||
type: object
|
||||
models.KeyTokenJSON:
|
||||
properties:
|
||||
all_channels:
|
||||
type: boolean
|
||||
channels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
keytoken_id:
|
||||
type: string
|
||||
messages_sent:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
owner_user_id:
|
||||
type: string
|
||||
permissions:
|
||||
type: string
|
||||
timestamp_created:
|
||||
type: string
|
||||
timestamp_lastused:
|
||||
type: string
|
||||
type: object
|
||||
models.MessageJSON:
|
||||
properties:
|
||||
channel_id:
|
||||
@ -468,8 +606,6 @@ definitions:
|
||||
type: object
|
||||
models.UserJSON:
|
||||
properties:
|
||||
admin_key:
|
||||
type: string
|
||||
default_channel:
|
||||
type: string
|
||||
is_pro:
|
||||
@ -482,10 +618,6 @@ definitions:
|
||||
type: integer
|
||||
quota_used:
|
||||
type: integer
|
||||
read_key:
|
||||
type: string
|
||||
send_key:
|
||||
type: string
|
||||
timestamp_created:
|
||||
type: string
|
||||
timestamp_lastread:
|
||||
@ -497,7 +629,7 @@ definitions:
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
models.UserJSONWithClients:
|
||||
models.UserJSONWithClientsAndKeys:
|
||||
properties:
|
||||
admin_key:
|
||||
type: string
|
||||
@ -544,10 +676,6 @@ paths:
|
||||
description: All parameter can be set via query-parameter or the json body.
|
||||
Only UserID, UserKey and Title are required
|
||||
parameters:
|
||||
- example: qhnUbKcLgp6tg
|
||||
in: query
|
||||
name: chan_key
|
||||
type: string
|
||||
- example: test
|
||||
in: query
|
||||
name: channel
|
||||
@ -556,6 +684,10 @@ paths:
|
||||
in: query
|
||||
name: content
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: query
|
||||
name: key
|
||||
type: string
|
||||
- example: db8b0e6a-a08c-4646
|
||||
in: query
|
||||
name: msg_id
|
||||
@ -584,19 +716,11 @@ paths:
|
||||
in: query
|
||||
name: user_id
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: query
|
||||
name: user_key
|
||||
type: string
|
||||
- description: ' '
|
||||
in: body
|
||||
name: post_body
|
||||
schema:
|
||||
$ref: '#/definitions/handler.SendMessage.combined'
|
||||
- example: qhnUbKcLgp6tg
|
||||
in: formData
|
||||
name: chan_key
|
||||
type: string
|
||||
- example: test
|
||||
in: formData
|
||||
name: channel
|
||||
@ -605,6 +729,10 @@ paths:
|
||||
in: formData
|
||||
name: content
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: formData
|
||||
name: key
|
||||
type: string
|
||||
- example: db8b0e6a-a08c-4646
|
||||
in: formData
|
||||
name: msg_id
|
||||
@ -633,10 +761,6 @@ paths:
|
||||
in: formData
|
||||
name: user_id
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: formData
|
||||
name: user_key
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@ -1240,7 +1364,7 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserJSONWithClients'
|
||||
$ref: '#/definitions/models.UserJSONWithClientsAndKeys'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
@ -1252,6 +1376,193 @@ paths:
|
||||
summary: Create a new user
|
||||
tags:
|
||||
- API-v2
|
||||
/api/v2/users/:uid/keys:
|
||||
get:
|
||||
description: The request must be done with an ADMIN key, the returned keys are
|
||||
without their token.
|
||||
operationId: api-tokenkeys-list
|
||||
parameters:
|
||||
- description: UserID
|
||||
in: path
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ListUserKeys.response'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"401":
|
||||
description: user is not authorized / has missing permissions
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"404":
|
||||
description: message not found
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: List keys of the user
|
||||
tags:
|
||||
- API-v2
|
||||
post:
|
||||
operationId: api-tokenkeys-create
|
||||
parameters:
|
||||
- description: UserID
|
||||
in: path
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
- description: ' '
|
||||
in: body
|
||||
name: post_body
|
||||
schema:
|
||||
$ref: '#/definitions/handler.CreateUserKey.body'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.KeyTokenJSON'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"401":
|
||||
description: user is not authorized / has missing permissions
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"404":
|
||||
description: message not found
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Create a new key
|
||||
tags:
|
||||
- API-v2
|
||||
/api/v2/users/:uid/keys/:kid:
|
||||
delete:
|
||||
description: Cannot be used to delete the key used in the request itself
|
||||
operationId: api-tokenkeys-delete
|
||||
parameters:
|
||||
- description: UserID
|
||||
in: path
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
- description: TokenKeyID
|
||||
in: path
|
||||
name: kid
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.KeyTokenJSON'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"401":
|
||||
description: user is not authorized / has missing permissions
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"404":
|
||||
description: message not found
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Delete a key
|
||||
tags:
|
||||
- API-v2
|
||||
get:
|
||||
description: The request must be done with an ADMIN key, the returned key does
|
||||
not include its token.
|
||||
operationId: api-tokenkeys-get
|
||||
parameters:
|
||||
- description: UserID
|
||||
in: path
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
- description: TokenKeyID
|
||||
in: path
|
||||
name: kid
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.KeyTokenJSON'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"401":
|
||||
description: user is not authorized / has missing permissions
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"404":
|
||||
description: message not found
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Get a single key
|
||||
tags:
|
||||
- API-v2
|
||||
patch:
|
||||
operationId: api-tokenkeys-update
|
||||
parameters:
|
||||
- description: UserID
|
||||
in: path
|
||||
name: uid
|
||||
required: true
|
||||
type: integer
|
||||
- description: TokenKeyID
|
||||
in: path
|
||||
name: kid
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.KeyTokenJSON'
|
||||
"400":
|
||||
description: supplied values/parameters cannot be parsed / are invalid
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"401":
|
||||
description: user is not authorized / has missing permissions
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"404":
|
||||
description: message not found
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
"500":
|
||||
description: internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/ginresp.apiError'
|
||||
summary: Update a key
|
||||
tags:
|
||||
- API-v2
|
||||
/api/v2/users/{uid}:
|
||||
get:
|
||||
operationId: api-user-get
|
||||
@ -1956,10 +2267,6 @@ paths:
|
||||
description: All parameter can be set via query-parameter or the json body.
|
||||
Only UserID, UserKey and Title are required
|
||||
parameters:
|
||||
- example: qhnUbKcLgp6tg
|
||||
in: query
|
||||
name: chan_key
|
||||
type: string
|
||||
- example: test
|
||||
in: query
|
||||
name: channel
|
||||
@ -1968,6 +2275,10 @@ paths:
|
||||
in: query
|
||||
name: content
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: query
|
||||
name: key
|
||||
type: string
|
||||
- example: db8b0e6a-a08c-4646
|
||||
in: query
|
||||
name: msg_id
|
||||
@ -1996,19 +2307,11 @@ paths:
|
||||
in: query
|
||||
name: user_id
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: query
|
||||
name: user_key
|
||||
type: string
|
||||
- description: ' '
|
||||
in: body
|
||||
name: post_body
|
||||
schema:
|
||||
$ref: '#/definitions/handler.SendMessage.combined'
|
||||
- example: qhnUbKcLgp6tg
|
||||
in: formData
|
||||
name: chan_key
|
||||
type: string
|
||||
- example: test
|
||||
in: formData
|
||||
name: channel
|
||||
@ -2017,6 +2320,10 @@ paths:
|
||||
in: formData
|
||||
name: content
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: formData
|
||||
name: key
|
||||
type: string
|
||||
- example: db8b0e6a-a08c-4646
|
||||
in: formData
|
||||
name: msg_id
|
||||
@ -2045,10 +2352,6 @@ paths:
|
||||
in: formData
|
||||
name: user_id
|
||||
type: string
|
||||
- example: P3TNH8mvv14fm
|
||||
in: formData
|
||||
name: user_key
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
@ -439,18 +439,6 @@ func TestChannelUpdate(t *testing.T) {
|
||||
tt.AssertEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
|
||||
}
|
||||
|
||||
// [4] renew send_key
|
||||
|
||||
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels/%s", uid, chanid), gin.H{
|
||||
"send_key": true,
|
||||
})
|
||||
|
||||
{
|
||||
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels/%s", uid, chanid))
|
||||
tt.AssertNotEqual(t, "channels.subscribe_key", chan0["subscribe_key"], chan1["subscribe_key"])
|
||||
tt.AssertNotEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
|
||||
}
|
||||
|
||||
// [5] update description_name
|
||||
|
||||
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels/%s", uid, chanid), gin.H{
|
||||
|
@ -32,7 +32,6 @@ func TestGetClient(t *testing.T) {
|
||||
r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid)
|
||||
|
||||
tt.AssertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"]))
|
||||
tt.AssertEqual(t, "admin_key", admintok, r1["admin_key"])
|
||||
tt.AssertEqual(t, "username", nil, r1["username"])
|
||||
|
||||
type rt2 struct {
|
||||
|
@ -672,7 +672,7 @@ func TestCompatRequery(t *testing.T) {
|
||||
|
||||
r6 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_id": useridnew,
|
||||
"user_key": userkey,
|
||||
"key": userkey,
|
||||
"title": "HelloWorld_001",
|
||||
"msg_id": "r6",
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ func TestDeleteMessage(t *testing.T) {
|
||||
admintok := r0["admin_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Message_1",
|
||||
})
|
||||
@ -82,7 +82,7 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
|
||||
admintok := r0["admin_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Message_1",
|
||||
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
|
||||
@ -93,7 +93,7 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
|
||||
tt.RequestAuthGet[tt.Void](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
|
||||
|
||||
msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Message_1",
|
||||
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
|
||||
@ -106,7 +106,7 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
|
||||
// even though message is deleted, we still get a `suppress_send` on send_message
|
||||
|
||||
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Message_1",
|
||||
"msg_id": "bef8dd3d-078e-4f89-abf4-5258ad22a2e4",
|
||||
@ -123,7 +123,7 @@ func TestGetMessageSimple(t *testing.T) {
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": data.User[0].SendKey,
|
||||
"key": data.User[0].SendKey,
|
||||
"user_id": data.User[0].UID,
|
||||
"title": "Message_1",
|
||||
})
|
||||
@ -163,7 +163,7 @@ func TestGetMessageFull(t *testing.T) {
|
||||
content := tt.ShortLipsum0(2)
|
||||
|
||||
msgOut := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": data.User[0].SendKey,
|
||||
"key": data.User[0].SendKey,
|
||||
"user_id": data.User[0].UID,
|
||||
"title": "Message_1",
|
||||
"content": content,
|
||||
|
@ -31,19 +31,19 @@ func TestSendSimpleMessageJSON(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_001",
|
||||
})
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": readtok,
|
||||
"key": readtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_001",
|
||||
}, 401, apierr.USER_AUTH_FAILED)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": "asdf",
|
||||
"key": "asdf",
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_001",
|
||||
}, 401, apierr.USER_AUTH_FAILED)
|
||||
@ -82,7 +82,7 @@ func TestSendSimpleMessageQuery(t *testing.T) {
|
||||
admintok := r0["admin_key"].(string)
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("Hello World 2134")), nil)
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", uid, sendtok, url.QueryEscape("Hello World 2134")), nil)
|
||||
|
||||
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
|
||||
tt.AssertStrRepEqual(t, "msg.title", "Hello World 2134", pusher.Last().Message.Title)
|
||||
@ -119,7 +119,7 @@ func TestSendSimpleMessageForm(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Hello World 9999 [$$$]",
|
||||
})
|
||||
@ -157,8 +157,8 @@ func TestSendSimpleMessageFormAndQuery(t *testing.T) {
|
||||
uid := r0["user_id"].(string)
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), tt.FormData{
|
||||
"user_key": "ERR",
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), tt.FormData{
|
||||
"key": "ERR",
|
||||
"user_id": "999999",
|
||||
"title": "2222222",
|
||||
})
|
||||
@ -185,8 +185,8 @@ func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
// query overwrite body
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{
|
||||
"user_key": "ERR",
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{
|
||||
"key": "ERR",
|
||||
"user_id": models.NewUserID(),
|
||||
"title": "2222222",
|
||||
})
|
||||
@ -215,13 +215,13 @@ func TestSendSimpleMessageAlt1(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_001",
|
||||
})
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/send", gin.H{
|
||||
"user_key": readtok,
|
||||
"key": readtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_001",
|
||||
}, 401, apierr.USER_AUTH_FAILED)
|
||||
@ -261,7 +261,7 @@ func TestSendContentMessage(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": "I am Content\nasdf",
|
||||
@ -306,7 +306,7 @@ func TestSendWithSendername(t *testing.T) {
|
||||
admintok := r0["admin_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_xyz",
|
||||
"content": "Unicode: 日本 - yäy\000\n\t\x00...",
|
||||
@ -360,7 +360,7 @@ func TestSendLongContent(t *testing.T) {
|
||||
}
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -416,7 +416,7 @@ func TestSendTooLongContent(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -445,7 +445,7 @@ func TestSendLongContentPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -459,7 +459,7 @@ func TestSendLongContentPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -474,7 +474,7 @@ func TestSendLongContentPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -488,7 +488,7 @@ func TestSendLongContentPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -502,7 +502,7 @@ func TestSendLongContentPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "HelloWorld_042",
|
||||
"content": longContent,
|
||||
@ -525,7 +525,7 @@ func TestSendTooLongTitle(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
}, 400, apierr.TITLE_TOO_LONG)
|
||||
@ -549,7 +549,7 @@ func TestSendIdempotent(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Hello SCN",
|
||||
"content": "mamma mia",
|
||||
@ -571,7 +571,7 @@ func TestSendIdempotent(t *testing.T) {
|
||||
tt.AssertEqual(t, "len(messages)", 1, len(msgList1.Messages))
|
||||
|
||||
msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Hello again",
|
||||
"content": "mother mia",
|
||||
@ -590,7 +590,7 @@ func TestSendIdempotent(t *testing.T) {
|
||||
tt.AssertEqual(t, "len(messages)", 1, len(msgList2.Messages))
|
||||
|
||||
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "Hello third",
|
||||
"content": "let me go",
|
||||
@ -628,7 +628,7 @@ func TestSendWithPriority(t *testing.T) {
|
||||
|
||||
{
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M_001",
|
||||
"content": "TestSendWithPriority#001",
|
||||
@ -646,7 +646,7 @@ func TestSendWithPriority(t *testing.T) {
|
||||
|
||||
{
|
||||
msg2 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M_002",
|
||||
"content": "TestSendWithPriority#002",
|
||||
@ -665,7 +665,7 @@ func TestSendWithPriority(t *testing.T) {
|
||||
|
||||
{
|
||||
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M_003",
|
||||
"content": "TestSendWithPriority#003",
|
||||
@ -684,7 +684,7 @@ func TestSendWithPriority(t *testing.T) {
|
||||
|
||||
{
|
||||
msg4 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M_004",
|
||||
"content": "TestSendWithPriority#004",
|
||||
@ -720,7 +720,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
admintok := r0["admin_key"].(string)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -728,7 +728,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -736,7 +736,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -744,7 +744,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -752,7 +752,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -760,7 +760,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -768,7 +768,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -776,7 +776,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -784,7 +784,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -792,7 +792,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -800,7 +800,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -808,7 +808,7 @@ func TestSendInvalidPriority(t *testing.T) {
|
||||
}, 400, apierr.INVALID_PRIO)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": admintok,
|
||||
"key": admintok,
|
||||
"user_id": uid,
|
||||
"title": "(title)",
|
||||
"content": "(content)",
|
||||
@ -838,7 +838,7 @@ func TestSendWithTimestamp(t *testing.T) {
|
||||
ts := time.Now().Unix() - int64(time.Hour.Seconds())
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": fmt.Sprintf("%s", uid),
|
||||
"title": "TTT",
|
||||
"timestamp": fmt.Sprintf("%d", ts),
|
||||
@ -892,83 +892,83 @@ func TestSendInvalidTimestamp(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": fmt.Sprintf("%s", uid),
|
||||
"title": "TTT",
|
||||
"timestamp": "-10000",
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": fmt.Sprintf("%s", uid),
|
||||
"title": "TTT",
|
||||
"timestamp": "0",
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": fmt.Sprintf("%s", uid),
|
||||
"title": "TTT",
|
||||
"timestamp": fmt.Sprintf("%d", time.Now().Unix()-int64(25*time.Hour.Seconds())),
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": fmt.Sprintf("%s", uid),
|
||||
"title": "TTT",
|
||||
"timestamp": fmt.Sprintf("%d", time.Now().Unix()+int64(25*time.Hour.Seconds())),
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "TTT",
|
||||
"timestamp": -10000,
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "TTT",
|
||||
"timestamp": 0,
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "TTT",
|
||||
"timestamp": time.Now().Unix() - int64(25*time.Hour.Seconds()),
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "TTT",
|
||||
"timestamp": time.Now().Unix() + int64(25*time.Hour.Seconds()),
|
||||
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
sendtok,
|
||||
uid,
|
||||
"TTT",
|
||||
-10000,
|
||||
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
sendtok,
|
||||
uid,
|
||||
"TTT",
|
||||
0,
|
||||
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
sendtok,
|
||||
uid,
|
||||
"TTT",
|
||||
time.Now().Unix()-int64(25*time.Hour.Seconds()),
|
||||
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?key=%s&user_id=%s&title=%s×tamp=%d",
|
||||
sendtok,
|
||||
uid,
|
||||
"TTT",
|
||||
@ -1003,7 +1003,7 @@ func TestSendToNewChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M0",
|
||||
})
|
||||
@ -1015,7 +1015,7 @@ func TestSendToNewChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M1",
|
||||
"content": tt.ShortLipsum0(4),
|
||||
@ -1029,7 +1029,7 @@ func TestSendToNewChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M2",
|
||||
"content": tt.ShortLipsum0(4),
|
||||
@ -1043,7 +1043,7 @@ func TestSendToNewChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M3",
|
||||
"channel": "test",
|
||||
@ -1082,7 +1082,7 @@ func TestSendToManualChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M0",
|
||||
})
|
||||
@ -1094,7 +1094,7 @@ func TestSendToManualChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M1",
|
||||
"content": tt.ShortLipsum0(4),
|
||||
@ -1119,7 +1119,7 @@ func TestSendToManualChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M2",
|
||||
"content": tt.ShortLipsum0(4),
|
||||
@ -1133,7 +1133,7 @@ func TestSendToManualChannel(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M3",
|
||||
"channel": "test",
|
||||
@ -1161,21 +1161,21 @@ func TestSendToTooLongChannel(t *testing.T) {
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M3",
|
||||
"channel": "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
})
|
||||
|
||||
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M3",
|
||||
"channel": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
})
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": "M3",
|
||||
"channel": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901",
|
||||
@ -1203,7 +1203,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
|
||||
|
||||
{
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1222,7 +1222,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
|
||||
for i := 0; i < 48; i++ {
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1237,7 +1237,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
|
||||
}
|
||||
|
||||
msg50 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1253,7 +1253,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
}, 403, apierr.QUOTA_REACHED)
|
||||
@ -1281,7 +1281,7 @@ func TestQuotaExceededPro(t *testing.T) {
|
||||
|
||||
{
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1300,7 +1300,7 @@ func TestQuotaExceededPro(t *testing.T) {
|
||||
for i := 0; i < 998; i++ {
|
||||
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1315,7 +1315,7 @@ func TestQuotaExceededPro(t *testing.T) {
|
||||
}
|
||||
|
||||
msg50 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
@ -1331,7 +1331,7 @@ func TestQuotaExceededPro(t *testing.T) {
|
||||
}
|
||||
|
||||
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
}, 403, apierr.QUOTA_REACHED)
|
||||
@ -1361,7 +1361,7 @@ func TestSendParallel(t *testing.T) {
|
||||
sem <- tt.Void{}
|
||||
}()
|
||||
tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"user_key": sendtok,
|
||||
"key": sendtok,
|
||||
"user_id": uid,
|
||||
"title": tt.ShortLipsum0(2),
|
||||
})
|
||||
|
@ -19,7 +19,6 @@ func TestCreateUserNoClient(t *testing.T) {
|
||||
tt.AssertEqual(t, "len(clients)", 0, len(r0["clients"].([]any)))
|
||||
|
||||
uid := fmt.Sprintf("%v", r0["user_id"])
|
||||
admintok := r0["admin_key"].(string)
|
||||
readtok := r0["read_key"].(string)
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
@ -29,7 +28,6 @@ func TestCreateUserNoClient(t *testing.T) {
|
||||
r1 := tt.RequestAuthGet[gin.H](t, readtok, baseUrl, "/api/v2/users/"+uid)
|
||||
|
||||
tt.AssertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"]))
|
||||
tt.AssertEqual(t, "admin_key", admintok, r1["admin_key"])
|
||||
}
|
||||
|
||||
func TestCreateUserDummyClient(t *testing.T) {
|
||||
@ -52,7 +50,6 @@ func TestCreateUserDummyClient(t *testing.T) {
|
||||
r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid)
|
||||
|
||||
tt.AssertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"]))
|
||||
tt.AssertEqual(t, "admin_key", admintok, r1["admin_key"])
|
||||
tt.AssertEqual(t, "username", nil, r1["username"])
|
||||
|
||||
type rt2 struct {
|
||||
@ -92,7 +89,6 @@ func TestCreateUserWithUsername(t *testing.T) {
|
||||
r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid)
|
||||
|
||||
tt.AssertEqual(t, "uid", uid, fmt.Sprintf("%v", r1["user_id"]))
|
||||
tt.AssertEqual(t, "admin_key", admintok, r1["admin_key"])
|
||||
tt.AssertEqual(t, "username", "my_user", r1["username"])
|
||||
}
|
||||
|
||||
@ -188,65 +184,6 @@ func TestFailedUgradeUserToPro(t *testing.T) {
|
||||
tt.RequestAuthPatchShouldFail(t, admintok0, baseUrl, "/api/v2/users/"+uid0, gin.H{"pro_token": "@INVALID"}, 400, apierr.INVALID_PRO_TOKEN)
|
||||
}
|
||||
|
||||
func TestRecreateKeys(t *testing.T) {
|
||||
_, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
|
||||
"agent_model": "DUMMY_PHONE",
|
||||
"agent_version": "4X",
|
||||
"client_type": "ANDROID",
|
||||
"fcm_token": "DUMMY_FCM",
|
||||
})
|
||||
tt.AssertEqual(t, "username", nil, r0["username"])
|
||||
|
||||
uid := fmt.Sprintf("%v", r0["user_id"])
|
||||
|
||||
admintok := r0["admin_key"].(string)
|
||||
readtok := r0["read_key"].(string)
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
tt.RequestAuthPatchShouldFail(t, readtok, baseUrl, "/api/v2/users/"+uid, gin.H{"read_key": true}, 401, apierr.USER_AUTH_FAILED)
|
||||
|
||||
tt.RequestAuthPatchShouldFail(t, sendtok, baseUrl, "/api/v2/users/"+uid, gin.H{"read_key": true}, 401, apierr.USER_AUTH_FAILED)
|
||||
|
||||
r1 := tt.RequestAuthPatch[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid, gin.H{})
|
||||
tt.AssertEqual(t, "admin_key", admintok, r1["admin_key"])
|
||||
tt.AssertEqual(t, "read_key", readtok, r1["read_key"])
|
||||
tt.AssertEqual(t, "send_key", sendtok, r1["send_key"])
|
||||
|
||||
r2 := tt.RequestAuthPatch[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid, gin.H{"read_key": true})
|
||||
tt.AssertEqual(t, "admin_key", admintok, r2["admin_key"])
|
||||
tt.AssertNotEqual(t, "read_key", readtok, r2["read_key"])
|
||||
tt.AssertEqual(t, "send_key", sendtok, r2["send_key"])
|
||||
readtok = r2["read_key"].(string)
|
||||
|
||||
r3 := tt.RequestAuthPatch[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid, gin.H{"read_key": true, "send_key": true})
|
||||
tt.AssertEqual(t, "admin_key", admintok, r3["admin_key"])
|
||||
tt.AssertNotEqual(t, "read_key", readtok, r3["read_key"])
|
||||
tt.AssertNotEqual(t, "send_key", sendtok, r3["send_key"])
|
||||
readtok = r3["read_key"].(string)
|
||||
sendtok = r3["send_key"].(string)
|
||||
|
||||
r4 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid)
|
||||
tt.AssertEqual(t, "admin_key", admintok, r4["admin_key"])
|
||||
tt.AssertEqual(t, "read_key", readtok, r4["read_key"])
|
||||
tt.AssertEqual(t, "send_key", sendtok, r4["send_key"])
|
||||
|
||||
r5 := tt.RequestAuthPatch[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid, gin.H{"admin_key": true})
|
||||
tt.AssertNotEqual(t, "admin_key", admintok, r5["admin_key"])
|
||||
tt.AssertEqual(t, "read_key", readtok, r5["read_key"])
|
||||
tt.AssertEqual(t, "send_key", sendtok, r5["send_key"])
|
||||
admintokNew := r5["admin_key"].(string)
|
||||
|
||||
tt.RequestAuthGetShouldFail(t, admintok, baseUrl, "/api/v2/users/"+uid, 401, apierr.USER_AUTH_FAILED)
|
||||
|
||||
r6 := tt.RequestAuthGet[gin.H](t, admintokNew, baseUrl, "/api/v2/users/"+uid)
|
||||
tt.AssertEqual(t, "admin_key", admintokNew, r6["admin_key"])
|
||||
tt.AssertEqual(t, "read_key", readtok, r6["read_key"])
|
||||
tt.AssertEqual(t, "send_key", sendtok, r6["send_key"])
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
t.SkipNow() // TODO DeleteUser Not implemented
|
||||
|
||||
|
@ -352,9 +352,9 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
|
||||
body["user_id"] = users[mex.User].UID
|
||||
switch mex.Key {
|
||||
case AKEY:
|
||||
body["user_key"] = users[mex.User].AdminKey
|
||||
body["key"] = users[mex.User].AdminKey
|
||||
case SKEY:
|
||||
body["user_key"] = users[mex.User].SendKey
|
||||
body["key"] = users[mex.User].SendKey
|
||||
}
|
||||
if mex.Content != "" {
|
||||
body["content"] = mex.Content
|
||||
|
@ -23,7 +23,7 @@
|
||||
<pre>
|
||||
curl \
|
||||
--data "user_id=${userid}" \
|
||||
--data "user_key=${userkey}" \
|
||||
--data "key=${key}" \
|
||||
--data "title=${message_title}" \
|
||||
--data "content=${message_body}" \
|
||||
--data "priority=${0|1|2}" \
|
||||
@ -36,7 +36,7 @@ curl \
|
||||
<pre>
|
||||
curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
|
||||
|
@ -26,11 +26,11 @@
|
||||
</p>
|
||||
<p>
|
||||
To receive them you will need to install the <a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier">SimpleCloudNotifier</a> app from the play store.
|
||||
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>user_key</code>.
|
||||
When you open the app you can click on the account tab to see you unique <code>user_id</code> and <code>key</code>.
|
||||
These two values are used to identify and authenticate your device so that send messages can be routed to your phone.
|
||||
</p>
|
||||
<p>
|
||||
You can at any time generate a new <code>user_key</code> in the app and invalidate the old one.
|
||||
You can at any time generate a new <code>key</code> in the app and invalidate the old one.
|
||||
</p>
|
||||
<p>
|
||||
There is also a <a href="/">web interface</a> for this API to manually send notifications to your phone or to test your setup.
|
||||
@ -52,7 +52,7 @@
|
||||
All Parameters can either directly be submitted as URL parameters or they can be put into the POST body (either multipart/form-data or JSON).
|
||||
</p>
|
||||
<p>
|
||||
You <i>need</i> to supply a valid <code>[user_id, user_key]</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
||||
You <i>need</i> to supply a valid <code>[user_id, key]</code> pair and a <code>title</code> for your message, all other parameter are optional.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">401 (Unauthorized)</td>
|
||||
<td data-label="Explanation">The user_id was not found or the user_key is wrong</td>
|
||||
<td data-label="Explanation">The user_id was not found or the key is wrong</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-label="Statuscode">403 (Forbidden)</td>
|
||||
@ -126,7 +126,7 @@
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
--data "content={message_content}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
@ -144,7 +144,7 @@
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
--data "priority={0|1|2}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
@ -159,7 +159,7 @@
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
--data "channel={my_channel}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
@ -179,7 +179,7 @@
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
--data "msg_id={message_id}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
@ -198,7 +198,7 @@
|
||||
</p>
|
||||
<pre>curl \
|
||||
--data "user_id={userid}" \
|
||||
--data "user_key={userkey}" \
|
||||
--data "key={key}" \
|
||||
--data "title={message_title}" \
|
||||
--data "timestamp={unix_timestamp}" \
|
||||
{{config|baseURL}}/</pre>
|
||||
|
@ -23,7 +23,7 @@ function send()
|
||||
|
||||
let data = new FormData();
|
||||
data.append('user_id', uid.value);
|
||||
data.append('user_key', key.value);
|
||||
data.append('key', key.value);
|
||||
if (tit.value !== '') data.append('title', tit.value);
|
||||
if (cnt.value !== '') data.append('content', cnt.value);
|
||||
if (pio.value !== '') data.append('priority', pio.value);
|
||||
|
@ -89,7 +89,7 @@ usage<span style="color: #f92672">()</span> <span style="color: #f92672">{</span
|
||||
--output /dev/null <span style="color: #ae81ff">\</span>
|
||||
--write-out <span style="color: #e6db74">"%{http_code}"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"user_id=$user_id"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"user_key=$user_key"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"key=$key"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"title=$title"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"timestamp=$sendtime"</span> <span style="color: #ae81ff">\</span>
|
||||
--data <span style="color: #e6db74">"content=$content"</span> <span style="color: #ae81ff">\</span>
|
||||
|
@ -26,7 +26,7 @@
|
||||
<span style="color: #008800; font-style: italic"># INSERT YOUR DATA HERE #</span>
|
||||
<span style="color: #008800; font-style: italic">################################################################################</span>
|
||||
user_id=<span style="color: #0000FF">"999"</span>
|
||||
user_key=<span style="color: #0000FF">"??"</span>
|
||||
key=<span style="color: #0000FF">"??"</span>
|
||||
<span style="color: #008800; font-style: italic">################################################################################</span>
|
||||
|
||||
usage() {
|
||||
@ -89,7 +89,7 @@ content=<span style="color: #0000FF">""</span>
|
||||
--output /dev/null <span style="color: #0000FF">\</span>
|
||||
--write-out <span style="color: #0000FF">"%{http_code}"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"user_id=$user_id"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"user_key=$user_key"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"key=$key"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"title=$title"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"timestamp=$sendtime"</span> <span style="color: #0000FF">\</span>
|
||||
--data <span style="color: #0000FF">"content=$content"</span> <span style="color: #0000FF">\</span>
|
||||
|
Loading…
Reference in New Issue
Block a user