bde148f286
* wip * some extra fixes * stuff kinda works? * need to figure out mocks * need to figure out mocks * fix db listener * gqlgen fix * minor gqlgen watch adjustment * lint fixes * delete old codegen file * converting migrations to ESM * getModuleDIrectory * vitest sort of works * added back ts-vitest * resolve gql double load * fixing test timeout configs * TSC lint fix * fix automate tests * moar debugging * debugging * more debugging * codegen update * server works * yargs migrated * chore(server): getting rid of global mocks for Server ESM (#5046) * got rid of email mock * got rid of comment mocks * got rid of multi region mocks * got rid of stripe mock * admin override mock updated * removed final mock * fixing import.meta.resolve calls * another import.meta.resolve fix * added requested test * nyc ESM fix * removed unneeded deps + linting * yarn lock forgot to commit * tryna fix flakyness * email capture util fix * sendEmail fix * fix TSX check * sender transporter fix + CR comments * merge main fix * test fixx * circleci fix * gqlgen bigint fix * error formatter fix * more error formatting improvements * esmloader added to Dockerfile * more dockerfile fixes * bg jobs fix
158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
import { ForbiddenError } from '@/modules/shared/errors'
|
|
import { isNullOrUndefined, isScope, Roles } from '@speckle/shared'
|
|
import {
|
|
getAppFactory,
|
|
getAllPublicAppsFactory,
|
|
getAllAppsCreatedByUserFactory,
|
|
getAllAppsAuthorizedByUserFactory,
|
|
createAppFactory,
|
|
updateAppFactory,
|
|
deleteAppFactory,
|
|
revokeExistingAppCredentialsForUserFactory
|
|
} from '@/modules/auth/repositories/apps'
|
|
import { db } from '@/db/knex'
|
|
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
|
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
|
|
|
const getApp = getAppFactory({ db })
|
|
const getAllPublicApps = getAllPublicAppsFactory({ db })
|
|
const getAllAppsCreatedByUser = getAllAppsCreatedByUserFactory({ db })
|
|
const getAllAppsAuthorizedByUser = getAllAppsAuthorizedByUserFactory({ db })
|
|
const createApp = createAppFactory({ db })
|
|
const updateApp = updateAppFactory({ db })
|
|
const deleteApp = deleteAppFactory({ db })
|
|
const revokeExistingAppCredentialsForUser = revokeExistingAppCredentialsForUserFactory({
|
|
db
|
|
})
|
|
|
|
export default {
|
|
Query: {
|
|
async app(_parent, args) {
|
|
const app = await getApp({ id: args.id })
|
|
return app
|
|
},
|
|
|
|
async apps() {
|
|
return await getAllPublicApps()
|
|
}
|
|
},
|
|
|
|
ServerApp: {
|
|
secret(parent, _args, context) {
|
|
if (
|
|
context.auth &&
|
|
parent.author &&
|
|
parent.author.id &&
|
|
parent.author.id === context.userId
|
|
)
|
|
return parent.secret
|
|
|
|
return 'App secrets are only revealed to their author 😉'
|
|
},
|
|
async scopes(parent, _args, context) {
|
|
if ('scopes' in parent && parent.scopes?.length) return parent.scopes
|
|
return await context.loaders.apps.getAppScopes.load(parent.id)
|
|
}
|
|
},
|
|
|
|
User: {
|
|
async authorizedApps(_parent, _args, context) {
|
|
const res = await getAllAppsAuthorizedByUser({ userId: context.userId! })
|
|
return res
|
|
},
|
|
async createdApps(_parent, _args, context) {
|
|
return await getAllAppsCreatedByUser({ userId: context.userId! })
|
|
}
|
|
},
|
|
Mutation: {
|
|
async appCreate(_parent, args, context) {
|
|
const { id } = await withOperationLogging(
|
|
async () =>
|
|
await createApp({
|
|
...args.app,
|
|
authorId: context.userId!,
|
|
public: isNullOrUndefined(args.app.public) ? undefined : args.app.public,
|
|
scopes: args.app.scopes.filter(isScope)
|
|
}),
|
|
{
|
|
operationName: 'appCreate',
|
|
operationDescription: 'Create a new app',
|
|
logger: context.log
|
|
}
|
|
)
|
|
return id
|
|
},
|
|
|
|
async appUpdate(_parent, args, context) {
|
|
const app = await getApp({ id: args.app.id })
|
|
if (!app) {
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
}
|
|
|
|
// only admins can update the default apps, generated by the server
|
|
if (!app?.author && context.role !== Roles.Server.Admin)
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
|
|
// only the author or an admin can update a 3rd party app
|
|
if (app?.author?.id !== context.userId && context.role !== Roles.Server.Admin)
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
|
|
await withOperationLogging(
|
|
async () =>
|
|
await updateApp({
|
|
app: {
|
|
...args.app,
|
|
public: isNullOrUndefined(args.app.public) ? undefined : args.app.public,
|
|
scopes: args.app.scopes.filter(isScope)
|
|
}
|
|
}),
|
|
{
|
|
operationName: 'appUpdate',
|
|
operationDescription: 'Update an existing app',
|
|
logger: context.log
|
|
}
|
|
)
|
|
return true
|
|
},
|
|
|
|
async appDelete(_parent, args, context) {
|
|
const app = await getApp({ id: args.appId })
|
|
if (!app) {
|
|
//Possibly ould have been an UserInputError, but
|
|
//we do not want to leak the existence of any app
|
|
//the user may not own or have access to.
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
}
|
|
|
|
if (!app.author && context.role !== Roles.Server.Admin)
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
if (app.author?.id !== context.userId && context.role !== Roles.Server.Admin)
|
|
throw new ForbiddenError('You are not authorized to edit this app.')
|
|
|
|
return await withOperationLogging(
|
|
async () => (await deleteApp({ id: args.appId })) === 1,
|
|
{
|
|
operationName: 'appDelete',
|
|
operationDescription: 'Delete an existing app',
|
|
logger: context.log
|
|
}
|
|
)
|
|
},
|
|
|
|
async appRevokeAccess(_parent, args, context) {
|
|
return await withOperationLogging(
|
|
async () =>
|
|
!!(await revokeExistingAppCredentialsForUser({
|
|
appId: args.appId,
|
|
userId: context.userId!
|
|
})),
|
|
{
|
|
operationName: 'appRevokeAccess',
|
|
operationDescription: 'Revoke access to an app',
|
|
logger: context.log
|
|
}
|
|
)
|
|
}
|
|
}
|
|
} as Resolvers
|