Прикрутил роли

This commit is contained in:
Александр Кирюхин 2024-01-21 02:20:59 +03:00
parent 05a70d8578
commit 5256756411
8 changed files with 152 additions and 79 deletions

View file

@ -38,7 +38,6 @@ paths:
$ref: '#/components/responses/errorResponse' $ref: '#/components/responses/errorResponse'
/user/logout: /user/logout:
post: post:
security: []
responses: responses:
204: 204:
description: "success logout" description: "success logout"
@ -48,7 +47,6 @@ paths:
# Game routes # Game routes
/games: /games:
get: get:
security: []
responses: responses:
200: 200:
$ref: '#/components/responses/gameListResponse' $ref: '#/components/responses/gameListResponse'
@ -91,8 +89,9 @@ paths:
$ref: '#/components/responses/taskResponse' $ref: '#/components/responses/taskResponse'
/file/upload: /file/upload:
post: post:
security: []
operationId: uploadFile operationId: uploadFile
security:
- cookieAuth: [creator, admin]
requestBody: requestBody:
content: content:
multipart/form-data: multipart/form-data:
@ -135,7 +134,36 @@ components:
format: uuid format: uuid
username: username:
type: string 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: gameView:
type: object type: object
properties: properties:
@ -339,36 +367,7 @@ components:
content: content:
'application/json': 'application/json':
schema: schema:
type: object $ref: "#/components/schemas/userView"
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
errorResponse: errorResponse:
description: '' description: ''
content: content:

View file

@ -98,6 +98,8 @@ func (w *ServerInterfaceWrapper) EnterCode(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) UploadFile(ctx echo.Context) error { func (w *ServerInterfaceWrapper) UploadFile(ctx echo.Context) error {
var err error var err error
ctx.Set(CookieAuthScopes, []string{"creator", "admin"})
// Invoke the callback with all the unmarshaled arguments // Invoke the callback with all the unmarshaled arguments
err = w.Handler.UploadFile(ctx) err = w.Handler.UploadFile(ctx)
return err return err
@ -125,6 +127,8 @@ func (w *ServerInterfaceWrapper) GetFile(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error { func (w *ServerInterfaceWrapper) GetGames(ctx echo.Context) error {
var err error var err error
ctx.Set(CookieAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments // Invoke the callback with all the unmarshaled arguments
err = w.Handler.GetGames(ctx) err = w.Handler.GetGames(ctx)
return err return err
@ -165,6 +169,8 @@ func (w *ServerInterfaceWrapper) PostUserLogin(ctx echo.Context) error {
func (w *ServerInterfaceWrapper) PostUserLogout(ctx echo.Context) error { func (w *ServerInterfaceWrapper) PostUserLogout(ctx echo.Context) error {
var err error var err error
ctx.Set(CookieAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments // Invoke the callback with all the unmarshaled arguments
err = w.Handler.PostUserLogout(ctx) err = w.Handler.PostUserLogout(ctx)
return err return err
@ -223,26 +229,26 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object // Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{ var swaggerSpec = []string{
"H4sIAAAAAAAC/8xYS2/jNhD+KwXbozbyPk66bYNtUDQo2jTbS2AEXGnscCORKjn0xgj03ws+JMsWacmq", "H4sIAAAAAAAC/8xYX2/bNhD/KgO3RzVy/zzprQu6YlgwbFm6l8AIWOnisJFIjTy5MQJ99+FISpYsypJV",
"m+wpDjWaxzffjGb4THJR1YIDR0WyZyLhHw0KfxYFA3sAHEFeigJu3BNzlguOwO1PWtclyykywdOvSnBz", "L91THPJ097vf/SGPzyxVRakkSDQseWYa/qnA4M8qE2AXQCLoS5XBtduhtVRJBGl/8rLMRcpRKBl/MUrS",
"pvIHqKj5VUtRg0SvKhcFmL+4rYFkRKFkfE2aJrFWmYSCZHdOapm0UuLLV8iRNPtiKDU0CVnTCj4VDOf4", "mkkfoOD0q9SqBI1eVaoyoL+4K4ElzKAWcsPqOrJWhYaMJbdOah01UurzF0iR1X0x1BXUEdvwAj5kApdg",
"9pOEFcnIj+kOgNQ9VWmrN2K2FGvG/wMQUFFWBpBISE2V+iZkMQ6T09F7YyJkEtZMIciXdn/38F3wqVYg", "+0nDPUvYj/GegNjtmrjRO2I2Vxshv4EIKLjIA0xErOTGfFU6m6bJ6eh8MZMyDRthEPRLw99vvgnuVga0",
"Oa0mEKSTTIYg9K1MAsSeqFpw5WOTUsgbf3I+rjOOsAZpAq1AKbqeWgg7+XA4Bahcstr4RDJCfE1cM4Wz", "5MWMBGkloyEJXSuzCLErplTSeN+0Vvrar5wv14VE2IAmRwswhm/mFsJePuxOBibVoiRMLGHM18SVMLjI",
"gmAIlZpSHX8z+GaseZeolHR7zKNZ3kxzImwUqXo8u1Gj9JhRXZeCFmegj9bMFtFKyIoiydxBMlYZRmgq", "CYFQmDnV8beAr2TNQ+Ja890xRIvQzAMRNorcPJ7dKCk9ZrQqc8WzM6RPVQlbRPdKFxxZ4haiqcogobl5",
"T0wZncHTeDOAp/pWXGopgeM1bKAM14MV+x2eRmRAMuB5pKYMGdQZ+JuQSbAnpIz7Or2RWdWBbtaLtrUU", "QmV0doJI6ThBdeTVtCVim+zcg+JA4VT9dIWjsWPFbVjILwAjZL89awb2j5uJmEjdxkSSRKxUwh/vw3ZE",
"QnMAXYvDNAY0ic9p16/sF2/qV/tA4VisfeEk9o13D2xqXsCNkP3uwz+wf9xMQljuHoxSpxbMz1pD7pj+", "KW1mlz5Ju4PxsPQjhgLzMF9uYbqeb0jukD+nNjoIp1XZoG899JyM0XzjcYCsClK9FRorTgdIKnDX+WwP",
"Mp3HRtpNKUMeI8MyjJc7GK+QWyN3iJ9Tmxyk06psve8i9JjEYL71fgDXlVG9YRI1NSzOGW57r+1c7wp3", "vW0zg+jwCh+Unk/dvjSG1KUaOEL2Hhek2wl5MKunTKfLpaokjmz/NzngcB5LhDYB9gC7tEZtuI6kiFF5",
"kB2q8UHI6dCZqou1gFwCRSg+4gy6ncCDiZ1mjC6XQnOMPP5/OOD8PEaEjgA7B/uwJl26jlBEiVIbreFq", "RVrD1cjv/a0p4DU84XQjcAq89DH7I/l2Hvshw21VB1vg/BRvO3ogxRvn5mvrhSPUb8JejyfhSGOxeiLv",
"pCs/wgaihiccbwROgZc+Zj/Ct/PYDxnuqjrYAqdTvOvoAYq3wU3XtpeOUL8JRx0nYaSxWD2JD7XvaAyq", "ahfoGFXjp8VpVI11g84NselU6vHOXweF3PJcZM2/Km9/SnjCuxy2QA2NSumO7OaAEOxsywMyhvu7BKRt",
"+NfiNKhi3aA3rredSjze+9mc8Q0tWdH+K8ruJ4cnvC97n+V7Y7cEhGBnm5+QmN+vkpCurQ4SMrHbzR2e", "qyeMKfBU3qjLSmuQeGUJC5aVFfsdniZkQAuQ6chtn+JgznCznt2883GsWuW9pCLqmi6p6BfPCiGDyTJ/",
"AiNPQhTkWjLc/mUga3khHhl81PhgnTKTkDsyKbN2iQKlet0sI7Rmv4FfbhhfCeubA5HwP+29Q0I2IJWb", "OrOoAiNah6gGZCgQA9YbCj38wO02YgbSSgvc/UU0NtWoHgW8r/DBkk/3T7dEhWIdYQaM6ZwhCeOl+A38",
"rN5eLC4WJhZRA6c1Ixl5f/H2YmF3U3ywbqTA14xD+qxZ0ZiDNdhkGcTs2P1rQTJyZYYRK2jflbQCBPOR", "FCPkvbLOutRl8k/7wBCxLWjj7rOvL1YXKyJHlSB5KVjC3l68vljZIRQfLIwY5EZIiJ8rkdW0sAFbIpSn",
"u/OuG307xx2k+/tt0pvbx3aJ5cEq/G6xiJGwk0v31qzGYrQXXNrObbVQgRA/tbdLLxdhe7+1jQfXuwJL", "9n79a8YS9pGugFbQfqt5AQh0tbj10EnfHriLfH+QjToX9KmhYX0w875ZrcYSs5WLe/NUbTnqORc3t+VS",
"B/dfzblgWrESUrcxxgH6bJ//wmwtHjjf29gqXSKrqcTUYPCmoBhY1YzBPZi+ME7lNrhWBm9OTo36YB3u", "mYCLH5pnpJfzsHnI2o0713nrigcPXfW5aLoXOcRuNBwn6JPd/0XYDngAvjOaFVWOouQaY+LgVcYxMD2S",
"1yTJ7pY7FEbqANAD8GpFEFmORY6AbxRKoNX+kjyO8WAls+nx3OgW2hggV1ZgTlIG90ODtCQRLl7aCe7K", "wR5Nn4XkehecH4NPJKd6fTD3dmvShrhbjbfDhlOv9zxNVAqgp+i7lcnInKxSBHxlUAMv+vPydBQGo7IN",
"LasnF9LhXW0z1/mY4/vd/c4NnMIMebSoGCfLxjHOfDSOQftZ2clwBt37NypNQj4s3o+/tH/h6NNvNKXd", "oM+e9gQZI+Rj0x9PDtvgqchaDOfnpb1Lf3RN/eTiOnyorZfC/dYcs2feETI/uTNxQQl0n1PqiL1bvZ3+",
"LXM4G38IZV29tmIzEuL0N+eJdHF6pIFm0MYtNE4K3MgN/P8wWNeJ0nkOSv3gVZ/b4/6d+nGfb1rJGfnq", "qP/a6ANOmuL2iTkcjT+UsVCvrNiCgDj99Xk8XZ3uaS94657fqsJZjpPcAP+7wcMJM1WagjE/eNVLEe8x",
"rHw/KTtagEvTTBXITduutSxJRlIzEzXL5t8AAAD//3AKhGJ6GgAA", "dp/Qj6O8biQXRKi18v8J0tGSW1PDNKC3TUuudM4SFtPNqF7X/wYAAP//R+pUyGkaAAA=",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View file

@ -26,6 +26,13 @@ const (
OldCode TaskViewMessage = "old_code" OldCode TaskViewMessage = "old_code"
) )
// Defines values for UserViewRole.
const (
Admin UserViewRole = "admin"
Creator UserViewRole = "creator"
User UserViewRole = "user"
)
// CodeEdit defines model for codeEdit. // CodeEdit defines model for codeEdit.
type CodeEdit struct { type CodeEdit struct {
Code string `json:"code"` Code string `json:"code"`
@ -98,10 +105,20 @@ type TaskViewMessage string
// UserView defines model for userView. // UserView defines model for userView.
type UserView struct { type UserView 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"` Id openapi_types.UUID `json:"id"`
Level int `json:"level"`
Role UserViewRole `json:"role"`
Username string `json:"username"` Username string `json:"username"`
} }
// UserViewRole defines model for UserView.Role.
type UserViewRole string
// ErrorResponse defines model for errorResponse. // ErrorResponse defines model for errorResponse.
type ErrorResponse struct { type ErrorResponse struct {
Code int `json:"code"` Code int `json:"code"`
@ -123,16 +140,7 @@ type UploadResponse struct {
} }
// UserResponse defines model for userResponse. // UserResponse defines model for userResponse.
type UserResponse struct { type UserResponse = UserView
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"`
}
// EnterCodeRequest defines model for enterCodeRequest. // EnterCodeRequest defines model for enterCodeRequest.
type EnterCodeRequest struct { type EnterCodeRequest struct {

View file

@ -13,6 +13,7 @@ import NoMatch from './pages/NoMatch'
import Engine from './pages/Engine' import Engine from './pages/Engine'
import Quests from './pages/Quests' import Quests from './pages/Quests'
import User from './pages/User' import User from './pages/User'
import { useRole } from './utils/roles'
const router = createBrowserRouter( const router = createBrowserRouter(
createRoutesFromElements( createRoutesFromElements(
@ -40,19 +41,17 @@ const router = createBrowserRouter(
element={<Auth><Engine /></Auth>} element={<Auth><Engine /></Auth>}
loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })} loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })}
/> />
{/* <Route <Route
path="admin" path="admin"
element={<Auth role="creator"><Admin /></Auth>} element={<Auth role="admin"><NoMatch /></Auth>}
loader={() => ajax(`/api/admin/games`)} // loader={() => ajax(`/api/admin/games`)}
/> />
<Route <Route
path="admin/games/new" path="quest/new"
element={<Auth role="creator"><AdminGame /></Auth>} element={<Auth role="creator"><NoMatch /></Auth>}
loader={() => ({ // loader={() => ajax(`/api/admin/games`)}
title: "Новая игра", />
tasks: []
})}
/> */}
<Route path="*" element={<NoMatch />} /> <Route path="*" element={<NoMatch />} />
</Route> </Route>
) )
@ -66,16 +65,22 @@ function App () {
function Auth (props) { function Auth (props) {
const baseUser = useRouteLoaderData('root') const baseUser = useRouteLoaderData('root')
const { user } = UserProvider.useContainer() const { user } = UserProvider.useContainer()
const { hasRole } = useRole()
const location = useLocation() const location = useLocation()
if (!user && !baseUser) { if (!user && !baseUser) {
return <Navigate to="/login" state={{ from: location }} replace /> return <Navigate to="/login" state={{ from: location }} replace />
} }
if (props.role && !hasRole(props.role)) {
return <Navigate to="/" replace />
}
return props.children return props.children
} }
Auth.propTypes = { Auth.propTypes = {
children: PropTypes.any children: PropTypes.any,
role: PropTypes.string
} }
export default App export default App

View file

@ -5,6 +5,7 @@ import { ajax } from '../utils/fetch'
import { Layout, Menu } from 'antd' import { Layout, Menu } from 'antd'
import { MenuOutlined } from '@ant-design/icons' import { MenuOutlined } from '@ant-design/icons'
import { Content, Header } from 'antd/es/layout/layout' import { Content, Header } from 'antd/es/layout/layout'
import { useRole } from '../utils/roles'
const AppLayout = () => { const AppLayout = () => {
const params = useLoaderData() const params = useLoaderData()
@ -14,6 +15,7 @@ const AppLayout = () => {
setUser(params) setUser(params)
}, [params]) }, [params])
const { user, setUser } = UserProvider.useContainer() const { user, setUser } = UserProvider.useContainer()
const { hasRole } = useRole()
const logout = () => { const logout = () => {
ajax('/api/user/logout', { ajax('/api/user/logout', {
@ -33,6 +35,20 @@ const AppLayout = () => {
label: 'Квесты', label: 'Квесты',
link: '/quests' link: '/quests'
}, },
hasRole('creator')
? {
key: 'quest/new',
label: 'Создать квест',
link: '/quest/new'
}
: null,
hasRole('admin')
? {
key: 'admin',
label: 'Админка',
link: '/admin'
}
: null,
{ {
key: 'me', key: 'me',
label: `${user.username} [${user.level}]`, label: `${user.username} [${user.level}]`,

17
main.go
View file

@ -84,6 +84,23 @@ func main() {
echoCtx := ctx.Value(oapiMiddleware.EchoContextKey).(echo.Context) echoCtx := ctx.Value(oapiMiddleware.EchoContextKey).(echo.Context)
user := appmiddleware.GetUser(echoCtx) user := appmiddleware.GetUser(echoCtx)
if user != nil { 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 return nil
} }

View file

@ -138,7 +138,15 @@ func mapUser(c echo.Context, user *models.User) error {
} }
level := utils.ExpToLevel(user.Experience) 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{ return c.JSON(http.StatusOK, &api.UserResponse{
Id: user.ID, Id: user.ID,
Username: user.Username, Username: user.Username,
@ -148,5 +156,6 @@ func mapUser(c echo.Context, user *models.User) error {
ExpToNextLevel: utils.LevelToExp(level + 1), ExpToNextLevel: utils.LevelToExp(level + 1),
Level: int(level), Level: int(level),
Games: games, Games: games,
Role: role,
}) })
} }

View file

@ -13,4 +13,17 @@ type User struct {
Password string `json:"-"` Password string `json:"-"`
Experience int Experience int
Games []*GameCursor Games []*GameCursor
Role UserRole
} }
func (u *User) HasRole(role UserRole) bool {
return u.Role >= role
}
type UserRole int
const (
RoleUser UserRole = iota
RoleCreator
RoleAdmin
)