From 362a6d10413fff332505af50d0be2519f5848214 Mon Sep 17 00:00:00 2001 From: Denis Istratov Date: Fri, 25 Oct 2024 12:00:00 +0300 Subject: [PATCH] update api.go, auth.go, docs.go, handler.go, service.go, swagger.json, swagger.yaml --- docs/docs.go | 413 +++++++++++++++++++++++++++++++++++++++++ docs/swagger.json | 389 ++++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 255 +++++++++++++++++++++++++ pkg/handler/api.go | 326 ++++++++++++++++++++++++++++++++ pkg/handler/handler.go | 80 ++++++++ pkg/service/auth.go | 77 ++++++++ pkg/service/service.go | 24 +++ 7 files changed, 1564 insertions(+) create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 pkg/handler/api.go create mode 100644 pkg/handler/handler.go create mode 100644 pkg/service/auth.go create mode 100644 pkg/service/service.go diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..0f95026 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,413 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "API Support", + "email": "red.tea.dev@gmail.com" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/user": { + "put": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Create new user", + "parameters": [ + { + "description": "User data in JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/esauth.User" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + } + }, + "/api/user/all": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get ids of users", + "parameters": [ + { + "type": "integer", + "description": "limit of users size", + "name": "limit", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "select offset", + "name": "start", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + } + }, + "/api/user/count": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get count of users", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.countResponse" + } + } + } + } + }, + "/api/user/infinity-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Create infinity token (only for admin)", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.infinityTokenResponce" + } + } + } + } + }, + "/api/user/self": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get current user", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Edit user", + "parameters": [ + { + "description": "User description JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.editInput" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + } + }, + "/api/user/{user}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get user by id", + "parameters": [ + { + "type": "integer", + "description": "search user by id", + "name": "user", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Edit user by id", + "parameters": [ + { + "type": "integer", + "description": "edit user by id", + "name": "user", + "in": "path", + "required": true + }, + { + "description": "User data in JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/esauth.User" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Delete user by id", + "parameters": [ + { + "type": "integer", + "description": "id of user", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "definitions": { + "esauth.User": { + "type": "object", + "properties": { + "access": { + "type": "string" + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "google_id": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "type": "string" + }, + "picture_url": { + "type": "string" + }, + "telegram_id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "handler.countResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + } + } + }, + "handler.editInput": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "picture_url": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "handler.infinityTokenResponce": { + "type": "object" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "auth.easystartup.su", + BasePath: "/api", + Schemes: []string{}, + Title: "EasyStartup Auth", + Description: "Auth server for EasyStartup.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..3da301b --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,389 @@ +{ + "swagger": "2.0", + "info": { + "description": "Auth server for EasyStartup.", + "title": "EasyStartup Auth", + "contact": { + "name": "API Support", + "email": "red.tea.dev@gmail.com" + }, + "version": "1.0" + }, + "host": "auth.easystartup.su", + "basePath": "/api", + "paths": { + "/api/user": { + "put": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Create new user", + "parameters": [ + { + "description": "User data in JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/esauth.User" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + } + }, + "/api/user/all": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get ids of users", + "parameters": [ + { + "type": "integer", + "description": "limit of users size", + "name": "limit", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "select offset", + "name": "start", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + } + }, + "/api/user/count": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get count of users", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.countResponse" + } + } + } + } + }, + "/api/user/infinity-token": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Create infinity token (only for admin)", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.infinityTokenResponce" + } + } + } + } + }, + "/api/user/self": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get current user", + "parameters": [ + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Edit user", + "parameters": [ + { + "description": "User description JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.editInput" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + } + }, + "/api/user/{user}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get user by id", + "parameters": [ + { + "type": "integer", + "description": "search user by id", + "name": "user", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Edit user by id", + "parameters": [ + { + "type": "integer", + "description": "edit user by id", + "name": "user", + "in": "path", + "required": true + }, + { + "description": "User data in JSON", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/esauth.User" + } + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/esauth.User" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Delete user by id", + "parameters": [ + { + "type": "integer", + "description": "id of user", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "JWT Bearer token (authorization)", + "name": "Authorization", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "definitions": { + "esauth.User": { + "type": "object", + "properties": { + "access": { + "type": "string" + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "google_id": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_name": { + "type": "string" + }, + "picture_url": { + "type": "string" + }, + "telegram_id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "handler.countResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer" + } + } + }, + "handler.editInput": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "picture_url": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "handler.infinityTokenResponce": { + "type": "object" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..130ee13 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,255 @@ +basePath: /api +definitions: + esauth.User: + properties: + access: + type: string + email: + type: string + first_name: + type: string + google_id: + type: string + id: + type: integer + last_name: + type: string + picture_url: + type: string + telegram_id: + type: string + username: + type: string + type: object + handler.countResponse: + properties: + count: + type: integer + type: object + handler.editInput: + properties: + email: + type: string + first_name: + type: string + last_name: + type: string + picture_url: + type: string + username: + type: string + type: object + handler.infinityTokenResponce: + type: object +host: auth.easystartup.su +info: + contact: + email: red.tea.dev@gmail.com + name: API Support + description: Auth server for EasyStartup. + title: EasyStartup Auth + version: "1.0" +paths: + /api/user: + put: + parameters: + - description: User data in JSON + in: body + name: user + required: true + schema: + $ref: '#/definitions/esauth.User' + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/esauth.User' + summary: Create new user + tags: + - user + /api/user/{user}: + delete: + parameters: + - description: id of user + in: path + name: id + required: true + type: integer + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + summary: Delete user by id + tags: + - user + get: + parameters: + - description: search user by id + in: path + name: user + required: true + type: integer + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/esauth.User' + summary: Get user by id + tags: + - user + post: + parameters: + - description: edit user by id + in: path + name: user + required: true + type: integer + - description: User data in JSON + in: body + name: user + required: true + schema: + $ref: '#/definitions/esauth.User' + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/esauth.User' + summary: Edit user by id + tags: + - user + /api/user/all: + get: + parameters: + - description: limit of users size + in: query + name: limit + required: true + type: integer + - description: select offset + in: query + name: start + required: true + type: integer + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + type: integer + type: array + summary: Get ids of users + tags: + - user + /api/user/count: + get: + parameters: + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.countResponse' + summary: Get count of users + tags: + - user + /api/user/infinity-token: + get: + parameters: + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.infinityTokenResponce' + summary: Create infinity token (only for admin) + tags: + - user + /api/user/self: + get: + parameters: + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/esauth.User' + summary: Get current user + tags: + - user + post: + parameters: + - description: User description JSON + in: body + name: user + required: true + schema: + $ref: '#/definitions/handler.editInput' + - description: JWT Bearer token (authorization) + in: header + name: Authorization + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/esauth.User' + summary: Edit user + tags: + - user +swagger: "2.0" diff --git a/pkg/handler/api.go b/pkg/handler/api.go new file mode 100644 index 0000000..3a1bc54 --- /dev/null +++ b/pkg/handler/api.go @@ -0,0 +1,326 @@ +package handler + +import ( + "esauth" + "esauth/pkg/util" + "net/http" + "net/mail" + "strconv" + + "github.com/gin-gonic/gin" +) + +// user godoc +// @Summary Get current user +// @Tags user +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} esauth.User +// @Router /api/user/self [get] +func (h *Handler) profile(c *gin.Context) { + id, _ := c.Get(userCtx) + user, err := h.repo.GetUser(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + c.JSON(http.StatusOK, user) +} + +// user/{user} godoc +// @Summary Get user by id +// @Tags user +// @Produce json +// @Param user path int true "search user by id" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} esauth.User +// @Router /api/user/{user} [get] +func (h *Handler) userById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + user, err := h.repo.GetUser(id) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + c.JSON(http.StatusOK, user) +} + +type editInput struct { + Username string `json:"username"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + PictureURL string `json:"picture_url"` +} + +// user/self godoc +// @Summary Edit user +// @Tags user +// @Produce json +// @Success 200 {object} esauth.User +// @Param user body handler.editInput true "User description JSON" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Router /api/user/self [post] +func (h *Handler) edit(c *gin.Context) { + id, _ := c.Get(userCtx) + user, err := h.repo.GetUser(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + var input editInput + + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + if input.Username != "" { + user.Username = input.Username + } + if input.FirstName != "" { + user.FirstName = input.FirstName + } + if input.LastName != "" { + user.LastName = input.LastName + } + if input.Email != "" { + if valid(input.Email) { + user.Email = input.Email + } else { + util.NewErrorResponse(c, http.StatusBadRequest, "email is invalid") + return + } + } + if input.PictureURL != "" { + user.PictureURL = input.PictureURL + } + + if _, err = h.repo.SaveUser(&user); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, user) +} + +// user/{user} godoc +// @Summary Edit user by id +// @Tags user +// @Produce json +// @Param user path int true "edit user by id" +// @Param user body esauth.User true "User data in JSON" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} esauth.User +// @Router /api/user/{user} [post] +func (h *Handler) editById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + _, err = h.repo.GetUser(id) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + var input esauth.User + + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + if input.Email != "" { + if !valid(input.Email) { + util.NewErrorResponse(c, http.StatusBadRequest, "email is invalid") + return + } + } + + if _, err = h.repo.SaveUser(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, input) +} + +func valid(email string) bool { + _, err := mail.ParseAddress(email) + return err == nil +} + +// user/all godoc +// @Summary Get ids of users +// @Tags user +// @Produce json +// @Param limit query int true "limit of users size" +// @Param start query int true "select offset" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {array} int +// @Router /api/user/all [get] +func (h *Handler) all(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + limit, err := strconv.Atoi(c.Query("limit")) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + start, err := strconv.Atoi(c.Query("start")) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, h.repo.GetUsers(start, limit)) +} + +// user/{user} godoc +// @Summary Delete user by id +// @Tags user +// @Produce json +// @Param id path int true "id of user" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/user/{user} [delete] +func (h *Handler) delete(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + _, err = h.repo.GetUser(id) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + err = h.repo.DeleteUser(id) + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, "{ \"status\": \"ok\" }") +} + +type countResponse struct { + Count int64 `json:"count"` +} + +// user/count godoc +// @Summary Get count of users +// @Tags user +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} handler.countResponse +// @Router /api/user/count [get] +func (h *Handler) count(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + c.JSON(http.StatusOK, countResponse{Count: h.repo.GetUsersCount()}) +} + +// user/ godoc +// @Summary Create new user +// @Tags user +// @Produce json +// @Param user body esauth.User true "User data in JSON" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} esauth.User +// @Router /api/user [put] +func (h *Handler) create(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.repo.GetUser(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + var input esauth.User + + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + if input.Email != "" { + if !valid(input.Email) { + util.NewErrorResponse(c, http.StatusBadRequest, "email is invalid") + return + } + } + + var id int64 + if id, err = h.repo.CreateUser(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + var user esauth.User + if user, err = h.repo.GetUser(id); err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, user) +} + +type infinityTokenResponce struct { + Token string `json:"token"` +} + +// user godoc +// @Summary Create infinity token (only for admin) +// @Tags user +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} handler.infinityTokenResponce +// @Router /api/user/infinity-token [get] +func (h *Handler) infinityToken(c *gin.Context) { + id, _ := c.Get(userCtx) + user, err := h.repo.GetUser(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + if user.Access != "admin" { + util.NewErrorResponse(c, http.StatusForbidden, "not admin") + return + } + token, err := h.services.GenerateInfinityToken(user.Id) + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + c.JSON(http.StatusOK, infinityTokenResponce{ + Token: token, + }) +} diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go new file mode 100644 index 0000000..5501e2f --- /dev/null +++ b/pkg/handler/handler.go @@ -0,0 +1,80 @@ +package handler + +import ( + "esauth/pkg/handler/oauth" + "esauth/pkg/repository" + "esauth/pkg/service" + "net/http" + + "esauth/docs" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +type Handler struct { + services *service.Service + repo *repository.Repository + telegramOAuth *oauth.TelegramOAuth + googleOAuth *oauth.GoogleOAuth +} + +func NewHandler(services *service.Service, repo *repository.Repository, telegramOAuth *oauth.TelegramOAuth, googleOAuth *oauth.GoogleOAuth) *Handler { + return &Handler{services: services, repo: repo, telegramOAuth: telegramOAuth, googleOAuth: googleOAuth} +} + +// @title EasyStartup Auth +// @version 1.0 +// @description Auth server for EasyStartup. +// +// @contact.name API Support +// @contact.email red.tea.dev@gmail.com +// +// @host auth.easystartup.su +// @BasePath /api +// + +func (h *Handler) InitRoutes() *gin.Engine { + router := gin.New() + docs.SwaggerInfo.BasePath = "/" + router.StaticFS("/static", http.Dir("./public/resources")) + router.LoadHTMLGlob("public/html/*") + + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", gin.H{ + "botId": h.telegramOAuth.BotId, + }) + }) + router.GET("/google-callback", func(c *gin.Context) { + c.HTML(http.StatusOK, "google-callback.html", gin.H{}) + }) + router.GET("/telegram-callback", func(c *gin.Context) { + c.HTML(http.StatusOK, "telegram-callback.html", gin.H{}) + }) + + router.GET("/telegram-callback-process", h.telegramOAuth.HandleTelegramOAuthCallback) + router.GET("/google-callback-process", h.googleOAuth.HandleGoogleCallback) + router.GET("/redir/google", func(c *gin.Context) { + url := h.googleOAuth.GoogleOauthConfig.AuthCodeURL(oauth.GOOGLE_RANDOM_STATE) + logrus.Info(url) + c.Redirect(http.StatusTemporaryRedirect, url) + }) + + api := router.Group("/api", h.userIdentity) + { + api.GET("user/self", h.profile) + api.POST("user/self", h.edit) + api.GET("user/all", h.all) + api.GET("user/count", h.count) + api.GET("user/infinity-token", h.infinityToken) + api.PUT("user", h.create) + api.GET("user/:id", h.userById) + api.POST("user/:id", h.editById) + api.DELETE("user/:id", h.delete) + } + + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + return router +} diff --git a/pkg/service/auth.go b/pkg/service/auth.go new file mode 100644 index 0000000..57fb07d --- /dev/null +++ b/pkg/service/auth.go @@ -0,0 +1,77 @@ +package service + +import ( + "errors" + "esauth" + "esauth/pkg/repository" + "math" + "os" + "time" + + "github.com/dgrijalva/jwt-go" +) + +const ( + tokenTTL = 12 * time.Hour +) + +type tokenClaims struct { + jwt.StandardClaims + UserId int64 `json:"user_id"` +} + +type AuthService struct { + repo *repository.Repository +} + +func NewAuthService(repo *repository.Repository) *AuthService { + return &AuthService{repo: repo} +} + +func (s *AuthService) CreateUser(user *esauth.User) (int64, error) { + return s.repo.CreateUser(user) +} +func (s *AuthService) SaveUser(user *esauth.User) (int64, error) { + return s.repo.SaveUser(user) +} +func (s *AuthService) GenerateToken(userId int64) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &tokenClaims{ + jwt.StandardClaims{ + ExpiresAt: time.Now().Add(tokenTTL).Unix(), + IssuedAt: time.Now().Unix(), + }, + userId, + }) + return token.SignedString([]byte(os.Getenv("SIGN_KEY"))) +} + +func (s *AuthService) GenerateInfinityToken(userId int64) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &tokenClaims{ + jwt.StandardClaims{ + ExpiresAt: math.MaxInt64, + IssuedAt: time.Now().Unix(), + }, + userId, + }) + return token.SignedString([]byte(os.Getenv("SIGN_KEY"))) +} + +func (s *AuthService) ParseToken(accessToken string) (int64, error) { + token, err := jwt.ParseWithClaims(accessToken, &tokenClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("invalid signing method") + } + + return []byte(os.Getenv("SIGN_KEY")), nil + }) + if err != nil { + return 0, err + } + + claims, ok := token.Claims.(*tokenClaims) + if !ok { + return 0, errors.New("token claims are not of type *tokenClaims") + } + + return claims.UserId, nil +} diff --git a/pkg/service/service.go b/pkg/service/service.go new file mode 100644 index 0000000..61f7e0a --- /dev/null +++ b/pkg/service/service.go @@ -0,0 +1,24 @@ +package service + +import ( + "esauth" + "esauth/pkg/repository" +) + +type Authorization interface { + CreateUser(user *esauth.User) (int64, error) + SaveUser(user *esauth.User) (int64, error) + GenerateToken(userId int64) (string, error) + GenerateInfinityToken(userId int64) (string, error) + ParseToken(token string) (int64, error) +} + +type Service struct { + Authorization +} + +func NewService(repos *repository.Repository) *Service { + return &Service{ + Authorization: NewAuthService(repos), + } +}