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

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'
/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:

View file

@ -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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",
}
// GetSwagger returns the content of the embedded swagger specification file

View file

@ -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 {
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 {

View file

@ -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={<Auth><Engine /></Auth>}
loader={({ params }) => ajax(`/api/engine/${params.gameId}`).catch(x => { console.log(x); return null })}
/>
{/* <Route
<Route
path="admin"
element={<Auth role="creator"><Admin /></Auth>}
loader={() => ajax(`/api/admin/games`)}
element={<Auth role="admin"><NoMatch /></Auth>}
// loader={() => ajax(`/api/admin/games`)}
/>
<Route
path="admin/games/new"
element={<Auth role="creator"><AdminGame /></Auth>}
loader={() => ({
title: "Новая игра",
tasks: []
})}
/> */}
path="quest/new"
element={<Auth role="creator"><NoMatch /></Auth>}
// loader={() => ajax(`/api/admin/games`)}
/>
<Route path="*" element={<NoMatch />} />
</Route>
)
@ -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 <Navigate to="/login" state={{ from: location }} replace />
}
if (props.role && !hasRole(props.role)) {
return <Navigate to="/" replace />
}
return props.children
}
Auth.propTypes = {
children: PropTypes.any
children: PropTypes.any,
role: PropTypes.string
}
export default App

View file

@ -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}]`,

17
main.go
View file

@ -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
}

View file

@ -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,
})
}

View file

@ -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
)