chore(logging): observability of operations related to auth

This commit is contained in:
Iain Sproat
2025-04-14 13:36:03 +01:00
parent 120a9a63b7
commit 149cc1871e
4 changed files with 81 additions and 32 deletions
@@ -12,6 +12,7 @@ import {
} 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 })
@@ -65,12 +66,20 @@ export = {
},
Mutation: {
async appCreate(_parent, args, context) {
const { id } = await createApp({
...args.app,
authorId: context.userId!,
public: isNullOrUndefined(args.app.public) ? undefined : args.app.public,
scopes: args.app.scopes.filter(isScope)
})
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
},
@@ -88,13 +97,21 @@ export = {
if (app?.author?.id !== context.userId && context.role !== Roles.Server.Admin)
throw new ForbiddenError('You are not authorized to edit this app.')
await updateApp({
app: {
...args.app,
public: isNullOrUndefined(args.app.public) ? undefined : args.app.public,
scopes: args.app.scopes.filter(isScope)
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
},
@@ -112,14 +129,29 @@ export = {
if (app.author?.id !== context.userId && context.role !== Roles.Server.Admin)
throw new ForbiddenError('You are not authorized to edit this app.')
return (await deleteApp({ id: args.appId })) === 1
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 revokeExistingAppCredentialsForUser({
appId: args.appId,
userId: context.userId!
}))
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
+28 -11
View File
@@ -37,6 +37,7 @@ import {
} from '@/modules/core/repositories/tokens'
import { getUserRoleFactory } from '@/modules/core/repositories/users'
import { corsMiddlewareFactory } from '@/modules/core/configs/cors'
import { withOperationLogging } from '@/observability/domain/businessLogging'
// TODO: Secure these endpoints!
export default function (app: Express) {
@@ -146,11 +147,19 @@ export default function (app: Express) {
if (!req.body.appId || !req.body.appSecret)
throw new BadRequestError('Invalid request - App Id and Secret are required.')
const authResponse = await refreshAppToken({
refreshToken: req.body.refreshToken,
appId: req.body.appId,
appSecret: req.body.appSecret
})
const authResponse = await withOperationLogging(
async () =>
await refreshAppToken({
refreshToken: req.body.refreshToken,
appId: req.body.appId,
appSecret: req.body.appSecret
}),
{
operationName: 'refreshAppToken',
operationDescription: 'Refresh an app token',
logger: req.log
}
)
return res.send(authResponse)
}
@@ -165,12 +174,20 @@ export default function (app: Express) {
`Invalid request, insufficient information provided in the request. App Id, Secret, Access Code, and Challenge are required.`
)
const authResponse = await createAppTokenFromAccessCode({
appId: req.body.appId,
appSecret: req.body.appSecret,
accessCode: req.body.accessCode,
challenge: req.body.challenge
})
const authResponse = await withOperationLogging(
async () =>
await createAppTokenFromAccessCode({
appId: req.body.appId,
appSecret: req.body.appSecret,
accessCode: req.body.accessCode,
challenge: req.body.challenge
}),
{
operationName: 'createAppTokenFromAccessCode',
operationDescription: 'Create an app token from an access code',
logger: req.log
}
)
return res.send(authResponse)
} catch (err) {
req.log.info({ err }, 'Error while trying to generate a new token.')
@@ -153,7 +153,7 @@ export const getBillingRouter = (): Router => {
operationName: 'completeCheckoutSession',
operationDescription:
'Payment succeeded or Stripe session completed, and payment was paid',
errorHandler: (err, logger) => {
errorHandler: async (err, logger) => {
if (err instanceof WorkspaceAlreadyPaidError) {
// ignore the request, this is prob a replay from stripe
logger.info('Workspace is already paid, ignoring')
@@ -18,9 +18,9 @@ export const withOperationLogging = async <T>(
logger: Logger
operationName: string
operationDescription?: string
errorHandler?: (err: unknown, logger: Logger) => MaybeAsync<unknown>
errorHandler?: (err: unknown, logger: Logger) => MaybeAsync<T>
}
): Promise<T | void> => {
): Promise<T> => {
const { operationName, operationDescription } = params
const errorHandler = params.errorHandler || logErrorThenThrow
const logger = params.logger.child(OperationName(operationName))
@@ -36,6 +36,6 @@ export const withOperationLogging = async <T>(
logger.info(OperationStatus.success, OperationLogLinePrefix)
return results
} catch (err) {
await errorHandler(err, logger)
return await errorHandler(err, logger)
}
}