diff --git a/server/.idea/sqldialects.xml b/server/.idea/sqldialects.xml index 8a4b714..90bf5af 100644 --- a/server/.idea/sqldialects.xml +++ b/server/.idea/sqldialects.xml @@ -1,7 +1,7 @@ - + diff --git a/server/api/apierr/enums.go b/server/api/apierr/enums.go index 8401c66..f82bf44 100644 --- a/server/api/apierr/enums.go +++ b/server/api/apierr/enums.go @@ -2,18 +2,19 @@ package apierr type APIError int +//goland:noinspection GoSnakeCaseUsage const ( NO_ERROR APIError = 0000 - MISSING_UID APIError = 1101 - MISSING_TOK APIError = 1102 - MISSING_TITLE APIError = 1103 - INVALID_PRIO APIError = 1104 - REQ_METHOD APIError = 1105 - INVALID_CLIENTTYPE APIError = 1106 - MISSING_QUERY_PARAM APIError = 1151 - MISSING_BODY_PARAM APIError = 1152 - MISSING_URI_PARAM APIError = 1153 + MISSING_UID APIError = 1101 + MISSING_TOK APIError = 1102 + MISSING_TITLE APIError = 1103 + INVALID_PRIO APIError = 1104 + REQ_METHOD APIError = 1105 + INVALID_CLIENTTYPE APIError = 1106 + BINDFAIL_QUERY_PARAM APIError = 1151 + BINDFAIL_BODY_PARAM APIError = 1152 + BINDFAIL_URI_PARAM APIError = 1153 NO_TITLE APIError = 1201 TITLE_TOO_LONG APIError = 1202 @@ -31,8 +32,9 @@ const ( FAILED_VERIFY_PRO_TOKEN APIError = 3001 INVALID_PRO_TOKEN APIError = 3002 - COMMIT_FAILED = 9001 - DATABASE_ERROR = 9002 + COMMIT_FAILED = 9001 + DATABASE_ERROR = 9002 + PERM_QUERY_FAIL = 9003 FIREBASE_COM_FAILED APIError = 9901 FIREBASE_COM_ERRORED APIError = 9902 diff --git a/server/api/handler/api.go b/server/api/handler/api.go index 12f03cc..5ab8bd6 100644 --- a/server/api/handler/api.go +++ b/server/api/handler/api.go @@ -5,6 +5,7 @@ import ( "blackforestbytes.com/simplecloudnotifier/api/models" "blackforestbytes.com/simplecloudnotifier/common/ginresp" "blackforestbytes.com/simplecloudnotifier/logic" + "database/sql" "github.com/gin-gonic/gin" "net/http" ) @@ -35,13 +36,12 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse { ClientType string `form:"client_type"` } - ctx := h.app.StartRequest(g) - defer ctx.Cancel() - var b body - if err := g.ShouldBindJSON(&b); err != nil { - return ginresp.InternAPIError(apierr.MISSING_BODY_PARAM, "Failed to read body", err) + ctx, errResp := h.app.StartRequest(g, nil, nil, &b) + if errResp != nil { + return *errResp } + defer ctx.Cancel() var clientType models.ClientType if b.ClientType == string(models.ClientTypeAndroid) { @@ -92,8 +92,46 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse { return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSON())) } +// GetUser swaggerdoc +// +// @Summary Create a new user +// @ID api-user-create +// +// @Param post_body body handler.CreateUser.body false " " +// @Param uid path int true "UserID" +// +// @Success 200 {object} models.UserJSON +// @Failure 400 {object} ginresp.apiError +// @Failure 401 {object} ginresp.apiError +// @Failure 404 {object} ginresp.apiError +// @Failure 500 {object} ginresp.apiError +// +// @Router /api-v2/user/{uid} [GET] func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse { - return ginresp.NotImplemented() + type uri struct { + UserID int64 `uri:"uid"` + } + + var u uri + ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + if errResp != nil { + return *errResp + } + defer ctx.Cancel() + + if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil { + return *permResp + } + + user, err := h.app.Database.GetUser(ctx, u.UserID) + if err == sql.ErrNoRows { + return ginresp.InternAPIError(apierr.USER_NOT_FOUND, "User not found", err) + } + if err != nil { + return ginresp.InternAPIError(apierr.DATABASE_ERROR, "Failed to query user", err) + } + + return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON())) } func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse { diff --git a/server/api/models/channel.go b/server/api/models/channel.go new file mode 100644 index 0000000..24ba15b --- /dev/null +++ b/server/api/models/channel.go @@ -0,0 +1,80 @@ +package models + +import ( + "database/sql" + "github.com/blockloop/scan" + "time" +) + +type Channel struct { + ChannelID int64 + OwnerUserID int64 + Name string + SubscribeKey string + SendKey string + TimestampCreated time.Time + TimestampLastRead *time.Time + TimestampLastSent *time.Time + MessagesSent int +} + +func (c Channel) JSON() ChannelJSON { + return ChannelJSON{ + ChannelID: c.ChannelID, + OwnerUserID: c.OwnerUserID, + Name: c.Name, + SubscribeKey: c.SubscribeKey, + SendKey: c.SendKey, + TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano), + TimestampLastRead: timeOptFmt(c.TimestampLastRead, time.RFC3339Nano), + TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano), + MessagesSent: c.MessagesSent, + } +} + +type ChannelJSON struct { + ChannelID int64 `json:"channel_id"` + OwnerUserID int64 `json:"owner_user_id"` + Name string `json:"name"` + SubscribeKey string `json:"subscribe_key"` + SendKey string `json:"send_key"` + TimestampCreated string `json:"timestamp_created"` + TimestampLastRead *string `json:"timestamp_last_read"` + TimestampLastSent *string `json:"timestamp_last_sent"` + MessagesSent int `json:"messages_sent"` +} + +type ChannelDB struct { + ChannelID int64 `db:"channel_id"` + OwnerUserID int64 `db:"owner_user_id"` + Name string `db:"name"` + SubscribeKey string `db:"subscribe_key"` + SendKey string `db:"send_key"` + TimestampCreated int64 `db:"timestamp_created"` + TimestampLastRead *int64 `db:"timestamp_last_read"` + TimestampLastSent *int64 `db:"timestamp_last_sent"` + MessagesSent int `db:"messages_sent"` +} + +func (c ChannelDB) Model() Channel { + return Channel{ + ChannelID: c.ChannelID, + OwnerUserID: c.OwnerUserID, + Name: c.Name, + SubscribeKey: c.SubscribeKey, + SendKey: c.SendKey, + TimestampCreated: time.UnixMilli(c.TimestampCreated), + TimestampLastRead: timeOptFromMilli(c.TimestampLastRead), + TimestampLastSent: timeOptFromMilli(c.TimestampLastSent), + MessagesSent: c.MessagesSent, + } +} + +func DecodeChannel(r *sql.Rows) (Channel, error) { + var udb ChannelDB + err := scan.RowStrict(&udb, r) + if err != nil { + return Channel{}, err + } + return udb.Model(), nil +} diff --git a/server/api/models/user.go b/server/api/models/user.go index cdb8919..dcc4bea 100644 --- a/server/api/models/user.go +++ b/server/api/models/user.go @@ -1,12 +1,16 @@ package models -import "time" +import ( + "database/sql" + "github.com/blockloop/scan" + "time" +) type User struct { UserID int64 Username *string - ReadKey string SendKey string + ReadKey string AdminKey string TimestampCreated time.Time TimestampLastRead *time.Time @@ -49,3 +53,45 @@ type UserJSON struct { QuotaDay *string `json:"quota_day"` IsPro bool `json:"is_pro"` } + +type UserDB struct { + UserID int64 `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"` + MessagesSent int `db:"messages_sent"` + QuotaToday int `db:"quota_today"` + QuotaDay *string `db:"quota_day"` + IsPro bool `db:"is_pro"` + ProToken *string `db:"pro_token"` +} + +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), + TimestampLastRead: timeOptFromMilli(u.TimestampLastRead), + TimestampLastSent: timeOptFromMilli(u.TimestampLastSent), + MessagesSent: u.MessagesSent, + QuotaToday: u.QuotaToday, + QuotaDay: u.QuotaDay, + IsPro: u.IsPro, + } +} + +func DecodeUser(r *sql.Rows) (User, error) { + var udb UserDB + err := scan.RowStrict(&udb, r) + if err != nil { + return User{}, err + } + return udb.Model(), nil +} diff --git a/server/api/models/utils.go b/server/api/models/utils.go index 6e2d0f4..051705c 100644 --- a/server/api/models/utils.go +++ b/server/api/models/utils.go @@ -12,3 +12,10 @@ func timeOptFmt(t *time.Time, fmt string) *string { return langext.Ptr(t.Format(fmt)) } } + +func timeOptFromMilli(millis *int64) *time.Time { + if millis == nil { + return nil + } + return langext.Ptr(time.UnixMilli(*millis)) +} diff --git a/server/db/database.go b/server/db/database.go index c3ce453..3de8c2c 100644 --- a/server/db/database.go +++ b/server/db/database.go @@ -11,14 +11,14 @@ import ( "time" ) -//go:embed schema_1.0.ddl -var schema_1_0 string +//go:embed schema_1.ddl +var schema1 string -//go:embed schema_2.0.ddl -var schema_2_0 string +//go:embed schema_2.ddl +var schema2 string -//go:embed schema_3.0.ddl -var schema_3_0 string +//go:embed schema_3.ddl +var schema3 string type Database struct { db *sql.DB @@ -40,7 +40,7 @@ func (db *Database) Migrate(ctx context.Context) error { schema, err := db.ReadSchema(ctx) if schema == 0 { - _, err = db.db.ExecContext(ctx, schema_3_0) + _, err = db.db.ExecContext(ctx, schema3) if err != nil { return err } diff --git a/server/db/methods.go b/server/db/methods.go index baf1a82..26f1185 100644 --- a/server/db/methods.go +++ b/server/db/methods.go @@ -2,6 +2,7 @@ package db import ( "blackforestbytes.com/simplecloudnotifier/api/models" + "database/sql" "gogs.mikescher.com/BlackForestBytes/goext/langext" "time" ) @@ -110,3 +111,66 @@ 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.QueryContext(ctx, "SELECT * FROM users WHERE admin_key = ? OR send_key = ? OR read_key = ? LIMIT 1", key, 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) GetChannelByKey(ctx TxContext, key string) (*models.Channel, error) { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return nil, err + } + + rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE subscribe_key = ? OR send_key = ? LIMIT 1", key, key) + 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) GetUser(ctx TxContext, userid int64) (models.User, error) { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return models.User{}, err + } + + rows, err := tx.QueryContext(ctx, "SELECT * FROM users WHERE user_id = ? LIMIT 1", userid) + if err != nil { + return models.User{}, err + } + + user, err := models.DecodeUser(rows) + if err != nil { + return models.User{}, err + } + + return user, nil +} diff --git a/server/db/schema_1.0.ddl b/server/db/schema_1.ddl similarity index 100% rename from server/db/schema_1.0.ddl rename to server/db/schema_1.ddl diff --git a/server/db/schema_2.0.ddl b/server/db/schema_2.ddl similarity index 100% rename from server/db/schema_2.0.ddl rename to server/db/schema_2.ddl diff --git a/server/db/schema_3.0.ddl b/server/db/schema_3.ddl similarity index 90% rename from server/db/schema_3.0.ddl rename to server/db/schema_3.ddl index 01b7d60..240bc62 100644 --- a/server/db/schema_3.0.ddl +++ b/server/db/schema_3.ddl @@ -4,8 +4,8 @@ CREATE TABLE users username TEXT NULL DEFAULT NULL, - read_key TEXT NOT NULL, send_key TEXT NOT NULL, + read_key TEXT NOT NULL, admin_key TEXT NOT NULL, timestamp_created INTEGER NOT NULL, @@ -51,21 +51,23 @@ CREATE TABLE channels subscribe_key TEXT NOT NULL, send_key TEXT NOT NULL, - messages_sent INTEGER NOT NULL DEFAULT '0', - timestamp_created INTEGER NOT NULL, timestamp_lastread INTEGER NULL DEFAULT NULL, - timestamp_lastsent INTEGER NULL DEFAULT NULL + timestamp_lastsent INTEGER NULL DEFAULT NULL, + + messages_sent INTEGER NOT NULL DEFAULT '0' ); CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, name); CREATE TABLE subscriptions ( - subscription_id INTEGER PRIMARY KEY AUTOINCREMENT, + subscription_id INTEGER PRIMARY KEY AUTOINCREMENT, - subscriber_user_id INTEGER NOT NULL, - channel_owner_user_id INTEGER NOT NULL, - channel_name TEXT NOT NULL + subscriber_user_id INTEGER NOT NULL, + channel_owner_user_id INTEGER NOT NULL, + channel_name TEXT NOT NULL, + + confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL DEFAULT 0 ); CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_name); diff --git a/server/go.mod b/server/go.mod index df8fd17..95461ab 100644 --- a/server/go.mod +++ b/server/go.mod @@ -12,6 +12,7 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/blockloop/scan v1.3.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect diff --git a/server/go.sum b/server/go.sum index b207928..7446a8f 100644 --- a/server/go.sum +++ b/server/go.sum @@ -4,6 +4,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/blockloop/scan v1.3.0 h1:p8xnajpGA3d/V6o23IBFdQ764+JnNJ+PQj+OwT+rkdg= +github.com/blockloop/scan v1.3.0/go.mod h1:qd+3w68+o7m5Xhj9X5SlJH2rbFyK8w0WT47Rkuer010= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +62,7 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= diff --git a/server/logic/application.go b/server/logic/application.go index d99f780..72d2c74 100644 --- a/server/logic/application.go +++ b/server/logic/application.go @@ -2,15 +2,19 @@ package logic import ( scn "blackforestbytes.com/simplecloudnotifier" + "blackforestbytes.com/simplecloudnotifier/api/apierr" + "blackforestbytes.com/simplecloudnotifier/common/ginresp" "blackforestbytes.com/simplecloudnotifier/db" "context" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" + "gogs.mikescher.com/BlackForestBytes/goext/langext" "math/rand" "net" "net/http" "os" "os/signal" + "strings" "syscall" "time" ) @@ -91,8 +95,79 @@ func (app *Application) Migrate() error { return app.Database.Migrate(ctx) } -func (app *Application) StartRequest(g *gin.Context) *AppContext { - ctx, cancel := context.WithTimeout(context.Background(), app.Config.RequestTimeout) +func (app *Application) StartRequest(g *gin.Context, uri any, query any, body any) (*AppContext, *ginresp.HTTPResponse) { - return &AppContext{inner: ctx, cancelFunc: cancel} + if body != nil { + if err := g.ShouldBindJSON(&body); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err)) + } + } + + if query != nil { + if err := g.ShouldBindQuery(&query); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(apierr.BINDFAIL_QUERY_PARAM, "Failed to read query", err)) + } + } + + if uri != nil { + if err := g.ShouldBindUri(&uri); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err)) + } + } + + ictx, cancel := context.WithTimeout(context.Background(), app.Config.RequestTimeout) + actx := CreateAppContext(ictx, cancel) + + authheader := g.GetHeader("Authorization") + + perm, err := app.getPermissions(actx, authheader) + if err != nil { + cancel() + return nil, langext.Ptr(ginresp.InternAPIError(apierr.PERM_QUERY_FAIL, "Failed to determine permissions", err)) + } + + actx.permissions = perm + + return actx, nil +} + +func (app *Application) getPermissions(ctx *AppContext, hdr string) (PermissionSet, error) { + if hdr == "" { + return NewEmptyPermissions(), nil + } + + if !strings.HasPrefix(hdr, "SCN ") { + return NewEmptyPermissions(), nil + } + + key := strings.TrimSpace(hdr[4:]) + + user, err := app.Database.GetUserByKey(ctx, key) + if err != nil { + return PermissionSet{}, err + } + + if user != nil && user.SendKey == key { + return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserSend}, nil + } + if user != nil && user.ReadKey == key { + return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserRead}, nil + } + if user != nil && user.AdminKey == key { + return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserAdmin}, nil + } + + channel, err := app.Database.GetChannelByKey(ctx, key) + if err != nil { + return PermissionSet{}, err + } + + if channel != nil && channel.SendKey == key { + return PermissionSet{ReferenceID: langext.Ptr(channel.ChannelID), KeyType: PermKeyTypeChannelSend}, nil + } + if channel != nil && channel.SubscribeKey == key { + return PermissionSet{ReferenceID: langext.Ptr(channel.ChannelID), KeyType: PermKeyTypeChannelSub}, nil + } + + return NewEmptyPermissions(), nil } diff --git a/server/logic/context.go b/server/logic/context.go index 3adfea2..a733473 100644 --- a/server/logic/context.go +++ b/server/logic/context.go @@ -15,6 +15,17 @@ type AppContext struct { cancelFunc context.CancelFunc cancelled bool transaction *sql.Tx + permissions PermissionSet +} + +func CreateAppContext(innerCtx context.Context, cancelFn context.CancelFunc) *AppContext { + return &AppContext{ + inner: innerCtx, + cancelFunc: cancelFn, + cancelled: false, + transaction: nil, + permissions: NewEmptyPermissions(), + } } func (ac *AppContext) Deadline() (deadline time.Time, ok bool) { diff --git a/server/logic/permissions.go b/server/logic/permissions.go new file mode 100644 index 0000000..eee80df --- /dev/null +++ b/server/logic/permissions.go @@ -0,0 +1,44 @@ +package logic + +import ( + "blackforestbytes.com/simplecloudnotifier/api/apierr" + "blackforestbytes.com/simplecloudnotifier/common/ginresp" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + +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 + PermKeyTypeChannelSub PermKeyType = "CHAN_SUBSCRIBE" // subscribe-channel + PermKeyTypeChannelSend PermKeyType = "CHAN_SEND" // send-messages +) + +type PermissionSet struct { + ReferenceID *int64 + KeyType PermKeyType +} + +func NewEmptyPermissions() PermissionSet { + return PermissionSet{ + ReferenceID: nil, + KeyType: PermKeyTypeNone, + } +} + +var respoNotAuthorized = ginresp.InternAPIError(apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil) + +func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse { + p := ac.permissions + if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserRead { + return nil + } + if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserAdmin { + return nil + } + + return langext.Ptr(respoNotAuthorized) +}