Files
speckle-server/packages/server/modules/auth/graph/resolvers/apps.ts
T
Kristaps Fabians Geikins bde148f286 chore(server): migrating fully to ESM (#5042)
* 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
2025-07-14 10:26:19 +03:00

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