mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 19:44:35 +00:00
feat: added get authorizedRoutes api endpoint
This commit is contained in:
@@ -548,6 +548,16 @@ components:
|
|||||||
- runTimes
|
- runTimes
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
AuthorizedRoutesResponse:
|
||||||
|
properties:
|
||||||
|
routes:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- routes
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
ExecuteReturnJsonPayload:
|
ExecuteReturnJsonPayload:
|
||||||
properties:
|
properties:
|
||||||
_program:
|
_program:
|
||||||
@@ -1593,6 +1603,24 @@ paths:
|
|||||||
- Info
|
- Info
|
||||||
security: []
|
security: []
|
||||||
parameters: []
|
parameters: []
|
||||||
|
/SASjsApi/info/authorizedRoutes:
|
||||||
|
get:
|
||||||
|
operationId: AuthorizedRoutes
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthorizedRoutesResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {routes: [/AppStream, /SASjsApi/stp/execute]}
|
||||||
|
summary: 'Get authorized routes.'
|
||||||
|
tags:
|
||||||
|
- Info
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
/SASjsApi/session:
|
/SASjsApi/session:
|
||||||
get:
|
get:
|
||||||
operationId: Session
|
operationId: Session
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Route, Tags, Example, Get } from 'tsoa'
|
import { Route, Tags, Example, Get } from 'tsoa'
|
||||||
|
import { getAuthorizedRoutes } from '../utils'
|
||||||
|
export interface AuthorizedRoutesResponse {
|
||||||
|
URIs: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface InfoResponse {
|
export interface InfoResponse {
|
||||||
mode: string
|
mode: string
|
||||||
@@ -36,4 +40,19 @@ export class InfoController {
|
|||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get authorized routes.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Example<AuthorizedRoutesResponse>({
|
||||||
|
URIs: ['/AppStream', '/SASjsApi/stp/execute']
|
||||||
|
})
|
||||||
|
@Get('/authorizedRoutes')
|
||||||
|
public authorizedRoutes(): AuthorizedRoutesResponse {
|
||||||
|
const response = {
|
||||||
|
URIs: getAuthorizedRoutes()
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,14 @@ infoRouter.get('/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
infoRouter.get('/authorizedRoutes', async (req, res) => {
|
||||||
|
const controller = new InfoController()
|
||||||
|
try {
|
||||||
|
const response = controller.authorizedRoutes()
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default infoRouter
|
export default infoRouter
|
||||||
|
|||||||
17
api/src/utils/getAuthorizedRoutes.ts
Normal file
17
api/src/utils/getAuthorizedRoutes.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export const getAuthorizedRoutes = () => {
|
||||||
|
const streamingApps = Object.keys(process.appStreamConfig)
|
||||||
|
const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`)
|
||||||
|
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes]
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticAuthorizedRoutes = [
|
||||||
|
'/AppStream',
|
||||||
|
'/SASjsApi/code/execute',
|
||||||
|
'/SASjsApi/stp/execute',
|
||||||
|
'/SASjsApi/drive/deploy',
|
||||||
|
'/SASjsApi/drive/upload',
|
||||||
|
'/SASjsApi/drive/file',
|
||||||
|
'/SASjsApi/drive/folder',
|
||||||
|
'/SASjsApi/drive/fileTree',
|
||||||
|
'/SASjsApi/permission'
|
||||||
|
]
|
||||||
@@ -8,6 +8,7 @@ export * from './file'
|
|||||||
export * from './generateAccessToken'
|
export * from './generateAccessToken'
|
||||||
export * from './generateAuthCode'
|
export * from './generateAuthCode'
|
||||||
export * from './generateRefreshToken'
|
export * from './generateRefreshToken'
|
||||||
|
export * from './getAuthorizedRoutes'
|
||||||
export * from './getCertificates'
|
export * from './getCertificates'
|
||||||
export * from './getDesktopFields'
|
export * from './getDesktopFields'
|
||||||
export * from './getPreProgramVariables'
|
export * from './getPreProgramVariables'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Joi from 'joi'
|
import Joi from 'joi'
|
||||||
import { PermissionSetting, PrincipalType } from '../controllers/permission'
|
import { PermissionSetting, PrincipalType } from '../controllers/permission'
|
||||||
|
import { getAuthorizedRoutes } from './getAuthorizedRoutes'
|
||||||
|
|
||||||
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
||||||
const passwordSchema = Joi.string().min(6).max(1024)
|
const passwordSchema = Joi.string().min(6).max(1024)
|
||||||
@@ -88,7 +89,9 @@ export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
|||||||
|
|
||||||
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
uri: Joi.string().required(),
|
uri: Joi.string()
|
||||||
|
.required()
|
||||||
|
.valid(...getAuthorizedRoutes()),
|
||||||
setting: Joi.string()
|
setting: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(PermissionSetting)),
|
.valid(...Object.values(PermissionSetting)),
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import React, {
|
import React, { useState, useEffect, Dispatch, SetStateAction } from 'react'
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
Dispatch,
|
|
||||||
SetStateAction
|
|
||||||
} from 'react'
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -13,15 +7,14 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
TextField,
|
TextField,
|
||||||
CircularProgress
|
CircularProgress,
|
||||||
|
Autocomplete
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { styled } from '@mui/material/styles'
|
import { styled } from '@mui/material/styles'
|
||||||
import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'
|
|
||||||
|
|
||||||
import { BootstrapDialogTitle } from '../../components/dialogTitle'
|
import { BootstrapDialogTitle } from '../../components/dialogTitle'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PermissionResponse,
|
|
||||||
UserResponse,
|
UserResponse,
|
||||||
GroupResponse,
|
GroupResponse,
|
||||||
RegisterPermissionPayload
|
RegisterPermissionPayload
|
||||||
@@ -39,18 +32,16 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({
|
|||||||
type AddPermissionModalProps = {
|
type AddPermissionModalProps = {
|
||||||
open: boolean
|
open: boolean
|
||||||
handleOpen: Dispatch<SetStateAction<boolean>>
|
handleOpen: Dispatch<SetStateAction<boolean>>
|
||||||
permissions: PermissionResponse[]
|
|
||||||
addPermission: (addPermissionPayload: RegisterPermissionPayload) => void
|
addPermission: (addPermissionPayload: RegisterPermissionPayload) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter = createFilterOptions<string>()
|
|
||||||
|
|
||||||
const AddPermissionModal = ({
|
const AddPermissionModal = ({
|
||||||
open,
|
open,
|
||||||
handleOpen,
|
handleOpen,
|
||||||
permissions,
|
|
||||||
addPermission
|
addPermission
|
||||||
}: AddPermissionModalProps) => {
|
}: AddPermissionModalProps) => {
|
||||||
|
const [URIs, setURIs] = useState<string[]>([])
|
||||||
|
const [loadingURIs, setLoadingURIs] = useState(false)
|
||||||
const [uri, setUri] = useState<string>()
|
const [uri, setUri] = useState<string>()
|
||||||
const [principalType, setPrincipalType] = useState('user')
|
const [principalType, setPrincipalType] = useState('user')
|
||||||
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
|
const [userPrincipal, setUserPrincipal] = useState<UserResponse>()
|
||||||
@@ -60,6 +51,23 @@ const AddPermissionModal = ({
|
|||||||
const [userPrincipals, setUserPrincipals] = useState<UserResponse[]>([])
|
const [userPrincipals, setUserPrincipals] = useState<UserResponse[]>([])
|
||||||
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
|
const [groupPrincipals, setGroupPrincipals] = useState<GroupResponse[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoadingURIs(true)
|
||||||
|
axios
|
||||||
|
.get('/SASjsApi/info/authorizedRoutes')
|
||||||
|
.then((res: any) => {
|
||||||
|
if (res.data) {
|
||||||
|
setURIs(res.data.URIs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoadingURIs(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoadingPrincipals(true)
|
setLoadingPrincipals(true)
|
||||||
axios
|
axios
|
||||||
@@ -97,12 +105,6 @@ const AddPermissionModal = ({
|
|||||||
addPermission(addPermissionPayload)
|
addPermission(addPermissionPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const URIs = useMemo(() => {
|
|
||||||
return permissions
|
|
||||||
.map((permission) => permission.uri)
|
|
||||||
.filter((uri, index, array) => array.indexOf(uri) === index)
|
|
||||||
}, [permissions])
|
|
||||||
|
|
||||||
const addButtonDisabled =
|
const addButtonDisabled =
|
||||||
!uri || (principalType === 'user' ? !userPrincipal : !groupPrincipal)
|
!uri || (principalType === 'user' ? !userPrincipal : !groupPrincipal)
|
||||||
|
|
||||||
@@ -118,29 +120,17 @@ const AddPermissionModal = ({
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
options={URIs}
|
||||||
disableClearable
|
disableClearable
|
||||||
value={uri}
|
value={uri}
|
||||||
onChange={(event, newValue) => setUri(newValue)}
|
onChange={(event: any, newValue: string) => setUri(newValue)}
|
||||||
filterOptions={(options, params) => {
|
renderInput={(params) =>
|
||||||
const filtered = filter(options, params)
|
loadingURIs ? (
|
||||||
|
<CircularProgress />
|
||||||
const { inputValue } = params
|
) : (
|
||||||
|
<TextField {...params} label="Principal" />
|
||||||
const isExisting = options.some(
|
|
||||||
(option) => inputValue === option
|
|
||||||
)
|
)
|
||||||
if (inputValue !== '' && !isExisting) {
|
|
||||||
filtered.push(inputValue)
|
|
||||||
}
|
}
|
||||||
return filtered
|
|
||||||
}}
|
|
||||||
selectOnFocus
|
|
||||||
clearOnBlur
|
|
||||||
handleHomeEndKeys
|
|
||||||
options={URIs}
|
|
||||||
renderOption={(props, option) => <li {...props}>{option}</li>}
|
|
||||||
freeSolo
|
|
||||||
renderInput={(params) => <TextField {...params} label="URI" />}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|||||||
@@ -330,7 +330,6 @@ const Permission = () => {
|
|||||||
<AddPermissionModal
|
<AddPermissionModal
|
||||||
open={addPermissionModalOpen}
|
open={addPermissionModalOpen}
|
||||||
handleOpen={setAddPermissionModalOpen}
|
handleOpen={setAddPermissionModalOpen}
|
||||||
permissions={permissions}
|
|
||||||
addPermission={addPermission}
|
addPermission={addPermission}
|
||||||
/>
|
/>
|
||||||
<UpdatePermissionModal
|
<UpdatePermissionModal
|
||||||
|
|||||||
Reference in New Issue
Block a user