Secure graphql endpoint
add option to scan individual users photos
This commit is contained in:
parent
25a00e93f1
commit
2a58f0b5ab
|
@ -1,7 +1,5 @@
|
||||||
# PhotoView
|
# 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)
|
[Follow the development progress here](https://www.notion.so/e8a95059eaa74fd7b8b69f8c949c9d08?v=c999e954941b43f4bf5fcec8451a789a)
|
||||||
|
|
||||||
![screenshot](/screenshot.png)
|
![screenshot](/screenshot.png)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import http from 'http'
|
||||||
import PhotoScanner from './scanner/Scanner'
|
import PhotoScanner from './scanner/Scanner'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
import { getUserFromToken, getTokenFromBearer } from './token'
|
import { getUserFromToken, getTokenFromBearer } from './token'
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ const scanner = new PhotoScanner(driver)
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
req.driver = driver
|
req.driver = driver
|
||||||
req.scanner = scanner
|
req.scanner = scanner
|
||||||
|
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -41,6 +43,30 @@ setInterval(scanner.scanAll, 1000 * 60 * 60 * 4)
|
||||||
// Specify port and path for GraphQL endpoint
|
// Specify port and path for GraphQL endpoint
|
||||||
const graphPath = '/graphql'
|
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)
|
const endpointUrl = new URL(config.host)
|
||||||
// endpointUrl.port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
// endpointUrl.port = process.env.GRAPHQL_LISTEN_PORT || 4001
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,38 @@ const Mutation = {
|
||||||
async scanAll(root, args, ctx, info) {
|
async scanAll(root, args, ctx, info) {
|
||||||
ctx.scanner.scanAll()
|
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 {
|
return {
|
||||||
finished: false,
|
finished: false,
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
@ -120,7 +120,7 @@ class PhotoScanner {
|
||||||
|
|
||||||
async scanUser(user) {
|
async scanUser(user) {
|
||||||
await _execScan(this, async () => {
|
await _execScan(this, async () => {
|
||||||
await _scanUser({ driver: this.driver, scanAlbum: this.scanAlbum }, user)
|
await _scanUser(this, user)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ type Mutation {
|
||||||
@neo4j_ignore
|
@neo4j_ignore
|
||||||
|
|
||||||
scanAll: ScannerResult! @isAuthenticated @neo4j_ignore
|
scanAll: ScannerResult! @isAuthenticated @neo4j_ignore
|
||||||
|
scanUser(userId: ID!): ScannerResult! @isAuthenticated @neo4j_ignore
|
||||||
|
|
||||||
initialSetupWizard(
|
initialSetupWizard(
|
||||||
username: String!
|
username: String!
|
||||||
|
@ -151,6 +152,7 @@ type Query {
|
||||||
siteInfo: SiteInfo
|
siteInfo: SiteInfo
|
||||||
|
|
||||||
myUser: User @isAuthenticated
|
myUser: User @isAuthenticated
|
||||||
|
user: [User] @hasRole(roles: [admin])
|
||||||
|
|
||||||
myAlbums: [Album] @isAuthenticated
|
myAlbums: [Album] @isAuthenticated
|
||||||
album(id: ID): Album @isAuthenticated
|
album(id: ID): Album @isAuthenticated
|
||||||
|
|
|
@ -51,6 +51,14 @@ const changeUserPasswordMutation = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const scanUserMutation = gql`
|
||||||
|
mutation scanUser($userId: ID!) {
|
||||||
|
scanUser(userId: $userId) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const ChangePasswordModal = ({ onClose, user, ...props }) => {
|
const ChangePasswordModal = ({ onClose, user, ...props }) => {
|
||||||
const [passwordInput, setPasswordInput] = useState('')
|
const [passwordInput, setPasswordInput] = useState('')
|
||||||
|
|
||||||
|
@ -223,6 +231,17 @@ const UserRow = ({ user, refetchUsers }) => {
|
||||||
<Icon name="edit" />
|
<Icon name="edit" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</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)}>
|
<Button onClick={() => setChangePassword(true)}>
|
||||||
<Icon name="key" />
|
<Icon name="key" />
|
||||||
Change password
|
Change password
|
||||||
|
|
|
@ -8,7 +8,7 @@ import AddUserRow from './AddUserRow'
|
||||||
|
|
||||||
const usersQuery = gql`
|
const usersQuery = gql`
|
||||||
query settingsUsersQuery {
|
query settingsUsersQuery {
|
||||||
User {
|
user {
|
||||||
id
|
id
|
||||||
username
|
username
|
||||||
rootPath
|
rootPath
|
||||||
|
@ -24,8 +24,8 @@ const UsersTable = () => {
|
||||||
<Query query={usersQuery}>
|
<Query query={usersQuery}>
|
||||||
{({ loading, error, data, refetch }) => {
|
{({ loading, error, data, refetch }) => {
|
||||||
let userRows = []
|
let userRows = []
|
||||||
if (data && data.User) {
|
if (data && data.user) {
|
||||||
userRows = data.User.map(user => (
|
userRows = data.user.map(user => (
|
||||||
<UserRow user={user} refetchUsers={refetch} key={user.id} />
|
<UserRow user={user} refetchUsers={refetch} key={user.id} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue