From 189c3ab273c49b477eef6c81395d0b61d5dc4f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Sat, 1 Jun 2024 13:22:56 +0200 Subject: [PATCH] Add route "/users/:uid/keys/current" --- scnserver/api/handler/apiKeyToken.go | 49 +++++++++++++++++++++++++++ scnserver/api/router.go | 1 + scnserver/models/keytoken.go | 2 +- scnserver/test/keytoken_test.go | 50 ++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/scnserver/api/handler/apiKeyToken.go b/scnserver/api/handler/apiKeyToken.go index 831b941..646625b 100644 --- a/scnserver/api/handler/apiKeyToken.go +++ b/scnserver/api/handler/apiKeyToken.go @@ -56,6 +56,55 @@ func (h APIHandler) ListUserKeys(g *gin.Context) ginresp.HTTPResponse { return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Keys: res})) } +// GetCurrentUserKey swaggerdoc +// +// @Summary Get the key currently used by this request +// @Description Can be done with keys of any permission - the returned key does not include its token. +// @ID api-tokenkeys-get-current +// @Tags API-v2 +// +// @Param uid path string true "UserID" +// @Param kid path string true "TokenKeyID" +// +// @Success 200 {object} models.KeyTokenWithTokenJSON +// @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/current [GET] +func (h APIHandler) GetCurrentUserKey(g *gin.Context) ginresp.HTTPResponse { + type uri struct { + UserID models.UserID `uri:"uid" 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.CheckPermissionAny(); permResp != nil { + return *permResp + } + + tokid := ctx.GetPermissionKeyTokenID() + if tokid == nil { + return ginresp.APIError(g, 400, apierr.USER_AUTH_FAILED, "Missing KeyTokenID in context", nil) + } + + keytoken, err := h.database.GetKeyToken(ctx, u.UserID, *tokid) + if errors.Is(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, keytoken.JSON().WithToken(keytoken.Token))) +} + // GetUserKey swaggerdoc // // @Summary Get a single key diff --git a/scnserver/api/router.go b/scnserver/api/router.go index 449ec12..b611a60 100644 --- a/scnserver/api/router.go +++ b/scnserver/api/router.go @@ -130,6 +130,7 @@ func (r *Router) Init(e *gin.Engine) error { 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/current", r.Wrap(r.apiHandler.GetCurrentUserKey)) 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)) diff --git a/scnserver/models/keytoken.go b/scnserver/models/keytoken.go index 071234c..f0d47f3 100644 --- a/scnserver/models/keytoken.go +++ b/scnserver/models/keytoken.go @@ -109,7 +109,7 @@ type KeyTokenWithTokenJSON struct { Token string `json:"token"` } -func (j KeyTokenJSON) WithToken(tok string) any { +func (j KeyTokenJSON) WithToken(tok string) KeyTokenWithTokenJSON { return KeyTokenWithTokenJSON{ KeyTokenJSON: j, Token: tok, diff --git a/scnserver/test/keytoken_test.go b/scnserver/test/keytoken_test.go index 48ae2ca..507256f 100644 --- a/scnserver/test/keytoken_test.go +++ b/scnserver/test/keytoken_test.go @@ -672,3 +672,53 @@ func TestTokenKeysCreateDefaultParam(t *testing.T) { tt.AssertEqual(t, "Channels.Len", 0, len(key2.Channels)) } } + +func TestTokenKeysGetCurrent(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + data := tt.InitSingleData(t, ws) + + type keyobj struct { + AllChannels bool `json:"all_channels"` + Channels []string `json:"channels"` + KeytokenId string `json:"keytoken_id"` + MessagesSent int `json:"messages_sent"` + Name string `json:"name"` + OwnerUserId string `json:"owner_user_id"` + Permissions string `json:"permissions"` + Token string `json:"token"` // only in create + } + type keylist struct { + Keys []keyobj `json:"keys"` + } + + klist := tt.RequestAuthGet[keylist](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", data.UID)) + tt.AssertEqual(t, "len(keys)", 3, len(klist.Keys)) + + keyAdmin := *langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Permissions == "A" }) + keySend := *langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Permissions == "CS" }) + keyRead := *langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Permissions == "UR;CR" }) + + { + currKey := tt.RequestAuthGet[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/current", data.UID)) + tt.AssertEqual(t, "currKey.KeytokenId", keyAdmin.KeytokenId, currKey.KeytokenId) + tt.AssertEqual(t, "currKey.Permissions", "A", currKey.Permissions) + tt.AssertEqual(t, "currKey.Token", data.AdminKey, currKey.Token) + } + + { + currKey := tt.RequestAuthGet[keyobj](t, data.SendKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/current", data.UID)) + tt.AssertEqual(t, "currKey.KeytokenId", keySend.KeytokenId, currKey.KeytokenId) + tt.AssertEqual(t, "currKey.Permissions", "CS", currKey.Permissions) + tt.AssertEqual(t, "currKey.Token", data.SendKey, currKey.Token) + } + + { + currKey := tt.RequestAuthGet[keyobj](t, data.ReadKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/current", data.UID)) + tt.AssertEqual(t, "currKey.KeytokenId", keyRead.KeytokenId, currKey.KeytokenId) + tt.AssertEqual(t, "currKey.Permissions", "UR;CR", currKey.Permissions) + tt.AssertEqual(t, "currKey.Token", data.ReadKey, currKey.Token) + } + +}