diff --git a/api/openapi.yaml b/api/openapi.yaml
index d8f4b39..e916808 100644
--- a/api/openapi.yaml
+++ b/api/openapi.yaml
@@ -38,7 +38,6 @@ paths:
$ref: '#/components/responses/errorResponse'
/user/logout:
post:
- security: []
responses:
204:
description: "success logout"
@@ -48,7 +47,6 @@ paths:
# Game routes
/games:
get:
- security: []
responses:
200:
$ref: '#/components/responses/gameListResponse'
@@ -91,8 +89,9 @@ paths:
$ref: '#/components/responses/taskResponse'
/file/upload:
post:
- security: []
operationId: uploadFile
+ security:
+ - cookieAuth: [creator, admin]
requestBody:
content:
multipart/form-data:
@@ -135,7 +134,36 @@ components:
format: uuid
username:
type: string
- required: [ id, username ]
+ email:
+ type: string
+ experience:
+ type: integer
+ level:
+ type: integer
+ expToCurrentLevel:
+ type: integer
+ expToNextLevel:
+ type: integer
+ games:
+ type: array
+ items:
+ $ref: "#/components/schemas/gameView"
+ role:
+ type: string
+ enum:
+ - user
+ - creator
+ - admin
+ required:
+ - id
+ - username
+ - email
+ - experience
+ - level
+ - expToCurrentLevel
+ - expToNextLevel
+ - games
+ - role
gameView:
type: object
properties:
@@ -339,36 +367,7 @@ components:
content:
'application/json':
schema:
- type: object
- properties:
- id:
- type: string
- format: uuid
- username:
- type: string
- email:
- type: string
- experience:
- type: integer
- level:
- type: integer
- expToCurrentLevel:
- type: integer
- expToNextLevel:
- type: integer
- games:
- type: array
- items:
- $ref: "#/components/schemas/gameView"
- required:
- - id
- - username
- - email
- - experience
- - level
- - expToCurrentLevel
- - expToNextLevel
- - games
+ $ref: "#/components/schemas/userView"
errorResponse:
description: ''
content:
diff --git a/api/server.go b/api/server.go
index 360d913..34d1157 100644
--- a/api/server.go
+++ b/api/server.go
@@ -98,6 +98,8 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) UploadFile(ctx echo.Context) error {
var err error
+ ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
+
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.UploadFile(ctx)
return err
@@ -125,6 +127,8 @@ func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error {
var err error
+ ctx.Set(CookieAuthScopes, []string{})
+
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.GetGames(ctx)
return err
@@ -165,6 +169,8 @@ func (w *ServerInterfaceWrapper) PostUserLogin(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) PostUserLogout(ctx echo.Context) error {
var err error
+ ctx.Set(CookieAuthScopes, []string{})
+
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.PostUserLogout(ctx)
return err
@@ -223,26 +229,26 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/api/types.go b/api/types.go
index 6aa564e..609673b 100644
--- a/api/types.go
+++ b/api/types.go
@@ -26,6 +26,13 @@ const (
OldCode TaskViewMessage = "old_code"
)
+// Defines values for UserViewRole.
+const (
+ Admin UserViewRole = "admin"
+ Creator UserViewRole = "creator"
+ User UserViewRole = "user"
+)
+
// CodeEdit defines model for codeEdit.
type CodeEdit struct {
Code string `json:"code"`
@@ -98,10 +105,20 @@ type TaskViewMessage string
// UserView defines model for userView.
type UserView struct {
- Id openapi_types.UUID `json:"id"`
- Username string `json:"username"`
+ Email string `json:"email"`
+ ExpToCurrentLevel int `json:"expToCurrentLevel"`
+ ExpToNextLevel int `json:"expToNextLevel"`
+ Experience int `json:"experience"`
+ Games []GameView `json:"games"`
+ Id openapi_types.UUID `json:"id"`
+ Level int `json:"level"`
+ Role UserViewRole `json:"role"`
+ Username string `json:"username"`
}
+// UserViewRole defines model for UserView.Role.
+type UserViewRole string
+
// ErrorResponse defines model for errorResponse.
type ErrorResponse struct {
Code int `json:"code"`
@@ -123,16 +140,7 @@ type UploadResponse struct {
}
// UserResponse defines model for userResponse.
-type UserResponse struct {
- Email string `json:"email"`
- ExpToCurrentLevel int `json:"expToCurrentLevel"`
- ExpToNextLevel int `json:"expToNextLevel"`
- Experience int `json:"experience"`
- Games []GameView `json:"games"`
- Id openapi_types.UUID `json:"id"`
- Level int `json:"level"`
- Username string `json:"username"`
-}
+type UserResponse = UserView
// EnterCodeRequest defines model for enterCodeRequest.
type EnterCodeRequest struct {
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 230cdba..42dc117 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -13,6 +13,7 @@ import NoMatch from './pages/NoMatch'
import Engine from './pages/Engine'
import Quests from './pages/Quests'
import User from './pages/User'
+import { useRole } from './utils/roles'
const router = createBrowserRouter(
createRoutesFromElements(
@@ -40,19 +41,17 @@ const router = createBrowserRouter(
element={}
loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })}
/>
- {/* }
- loader={() => ajax(`/api/admin/games`)}
+ element={}
+ // loader={() => ajax(`/api/admin/games`)}
/>
- }
- loader={() => ({
- title: "Новая игра",
- tasks: []
- })}
- /> */}
+ }
+ // loader={() => ajax(`/api/admin/games`)}
+ />
+
} />
)
@@ -66,16 +65,22 @@ function App () {
function Auth (props) {
const baseUser = useRouteLoaderData('root')
const { user } = UserProvider.useContainer()
+ const { hasRole } = useRole()
const location = useLocation()
if (!user && !baseUser) {
return
}
+ if (props.role && !hasRole(props.role)) {
+ return
+ }
+
return props.children
}
Auth.propTypes = {
- children: PropTypes.any
+ children: PropTypes.any,
+ role: PropTypes.string
}
export default App
diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx
index 8d88c8a..9279613 100644
--- a/frontend/src/components/Layout.jsx
+++ b/frontend/src/components/Layout.jsx
@@ -5,6 +5,7 @@ import { ajax } from '../utils/fetch'
import { Layout, Menu } from 'antd'
import { MenuOutlined } from '@ant-design/icons'
import { Content, Header } from 'antd/es/layout/layout'
+import { useRole } from '../utils/roles'
const AppLayout = () => {
const params = useLoaderData()
@@ -14,6 +15,7 @@ const AppLayout = () => {
setUser(params)
}, [params])
const { user, setUser } = UserProvider.useContainer()
+ const { hasRole } = useRole()
const logout = () => {
ajax('/api/user/logout', {
@@ -33,6 +35,20 @@ const AppLayout = () => {
label: 'Квесты',
link: '/quests'
},
+ hasRole('creator')
+ ? {
+ key: 'quest/new',
+ label: 'Создать квест',
+ link: '/quest/new'
+ }
+ : null,
+ hasRole('admin')
+ ? {
+ key: 'admin',
+ label: 'Админка',
+ link: '/admin'
+ }
+ : null,
{
key: 'me',
label: `${user.username} [${user.level}]`,
diff --git a/main.go b/main.go
index cd8552e..db83bf2 100644
--- a/main.go
+++ b/main.go
@@ -84,6 +84,23 @@ func main() {
echoCtx := ctx.Value(oapiMiddleware.EchoContextKey).(echo.Context)
user := appmiddleware.GetUser(echoCtx)
if user != nil {
+ if len(ai.Scopes) > 0 {
+ for _, v := range ai.Scopes {
+ switch v {
+ case "user":
+ return nil
+ case "creator":
+ if user.HasRole(models.RoleCreator) {
+ return nil
+ }
+ case "admin":
+ if user.HasRole(models.RoleAdmin) {
+ return nil
+ }
+ }
+ }
+ return echo.ErrForbidden
+ }
return nil
}
diff --git a/pkg/controller/user.go b/pkg/controller/user.go
index 67a00fc..2123096 100644
--- a/pkg/controller/user.go
+++ b/pkg/controller/user.go
@@ -138,7 +138,15 @@ func mapUser(c echo.Context, user *models.User) error {
}
level := utils.ExpToLevel(user.Experience)
-
+ role := api.User
+ switch user.Role {
+ case models.RoleUser:
+ role = api.User
+ case models.RoleCreator:
+ role = api.Creator
+ case models.RoleAdmin:
+ role = api.Admin
+ }
return c.JSON(http.StatusOK, &api.UserResponse{
Id: user.ID,
Username: user.Username,
@@ -148,5 +156,6 @@ func mapUser(c echo.Context, user *models.User) error {
ExpToNextLevel: utils.LevelToExp(level + 1),
Level: int(level),
Games: games,
+ Role: role,
})
}
diff --git a/pkg/models/user.go b/pkg/models/user.go
index 5d480d4..2d4d28c 100644
--- a/pkg/models/user.go
+++ b/pkg/models/user.go
@@ -13,4 +13,17 @@ type User struct {
Password string `json:"-"`
Experience int
Games []*GameCursor
+ Role UserRole
}
+
+func (u *User) HasRole(role UserRole) bool {
+ return u.Role >= role
+}
+
+type UserRole int
+
+const (
+ RoleUser UserRole = iota
+ RoleCreator
+ RoleAdmin
+)