Прикрутил роли
This commit is contained in:
parent
05a70d8578
commit
5256756411
8 changed files with 152 additions and 79 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
32
api/types.go
32
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 {
|
||||
|
|
|
@ -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: []
|
||||
})}
|
||||
/> */}
|
||||
<Route
|
||||
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
|
||||
|
|
|
@ -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
17
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue