1
Fork 0

Secure graphql endpoint

add option to scan individual users photos
This commit is contained in:
viktorstrate 2019-08-23 22:14:12 +02:00
parent 25a00e93f1
commit 2a58f0b5ab
7 changed files with 83 additions and 6 deletions

View File

@ -1,7 +1,5 @@
# PhotoView
> NOTE: This software should not be used in production yet, since it is in early development, and still contains security holes.
[Follow the development progress here](https://www.notion.so/e8a95059eaa74fd7b8b69f8c949c9d08?v=c999e954941b43f4bf5fcec8451a789a)
![screenshot](/screenshot.png)

View File

@ -7,6 +7,7 @@ import http from 'http'
import PhotoScanner from './scanner/Scanner'
import _ from 'lodash'
import config from './config'
import gql from 'graphql-tag'
import { getUserFromToken, getTokenFromBearer } from './token'
@ -32,6 +33,7 @@ const scanner = new PhotoScanner(driver)
app.use((req, res, next) => {
req.driver = driver
req.scanner = scanner
next()
})
@ -41,6 +43,30 @@ setInterval(scanner.scanAll, 1000 * 60 * 60 * 4)
// Specify port and path for GraphQL endpoint
const graphPath = '/graphql'
app.use(graphPath, (req, res, next) => {
if (req.body.query) {
const query = gql(req.body.query)
const defs = query.definitions.filter(x => x.kind == 'OperationDefinition')
const selections = defs.reduce((prev, curr) => {
return prev.concat(curr.selectionSet.selections)
}, [])
const names = selections.map(x => x.name.value)
const illegalNames = names.filter(
name => name.substr(0, 1) == name.substr(0, 1).match(/[A-Z]/)
)
if (illegalNames.length > 0) {
return res
.status(403)
.send({ error: `Illegal query, types not allowed: ${illegalNames}` })
}
}
next()
})
const endpointUrl = new URL(config.host)
// endpointUrl.port = process.env.GRAPHQL_LISTEN_PORT || 4001

View File

@ -4,6 +4,38 @@ const Mutation = {
async scanAll(root, args, ctx, info) {
ctx.scanner.scanAll()
return {
finished: false,
success: true,
progress: 0,
message: 'Starting scanner',
}
},
async scanUser(root, args, ctx, info) {
const session = ctx.driver.session()
const userResult = await session.run(
`MATCH (u:User { id: {userId} }) RETURN u`,
{
userId: args.userId,
}
)
session.close()
if (userResult.records.length == 0) {
return {
finished: false,
success: false,
progress: 0,
message: 'Could not scan user: User not found',
}
}
const user = userResult.records[0].get('u').properties
ctx.scanner.scanUser(user)
return {
finished: false,
success: true,

View File

@ -120,7 +120,7 @@ class PhotoScanner {
async scanUser(user) {
await _execScan(this, async () => {
await _scanUser({ driver: this.driver, scanAlbum: this.scanAlbum }, user)
await _scanUser(this, user)
})
}

View File

@ -130,6 +130,7 @@ type Mutation {
@neo4j_ignore
scanAll: ScannerResult! @isAuthenticated @neo4j_ignore
scanUser(userId: ID!): ScannerResult! @isAuthenticated @neo4j_ignore
initialSetupWizard(
username: String!
@ -151,6 +152,7 @@ type Query {
siteInfo: SiteInfo
myUser: User @isAuthenticated
user: [User] @hasRole(roles: [admin])
myAlbums: [Album] @isAuthenticated
album(id: ID): Album @isAuthenticated

View File

@ -51,6 +51,14 @@ const changeUserPasswordMutation = gql`
}
`
const scanUserMutation = gql`
mutation scanUser($userId: ID!) {
scanUser(userId: $userId) {
success
}
}
`
const ChangePasswordModal = ({ onClose, user, ...props }) => {
const [passwordInput, setPasswordInput] = useState('')
@ -223,6 +231,17 @@ const UserRow = ({ user, refetchUsers }) => {
<Icon name="edit" />
Edit
</Button>
<Mutation mutation={scanUserMutation}>
{(scanUser, { data, called }) => (
<Button
disabled={called}
onClick={() => scanUser({ variables: { userId: user.id } })}
>
<Icon name="sync" />
Scan
</Button>
)}
</Mutation>
<Button onClick={() => setChangePassword(true)}>
<Icon name="key" />
Change password

View File

@ -8,7 +8,7 @@ import AddUserRow from './AddUserRow'
const usersQuery = gql`
query settingsUsersQuery {
User {
user {
id
username
rootPath
@ -24,8 +24,8 @@ const UsersTable = () => {
<Query query={usersQuery}>
{({ loading, error, data, refetch }) => {
let userRows = []
if (data && data.User) {
userRows = data.User.map(user => (
if (data && data.user) {
userRows = data.user.map(user => (
<UserRow user={user} refetchUsers={refetch} key={user.id} />
))
}