Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): add url(protocol) validation to add callback mutation #3513

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/api/.env
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export KRATOS_PG_HOST="localhost"
export KRATOS_PG_PORT="5433"

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
# TODO: rename to OTEL_SERVICE_NAME
# TODO: rename to OTEL_SERVICE_NAME
# https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#otel_service_name
export TRACING_SERVICE_NAME="galoy-dev"

Expand Down
2 changes: 1 addition & 1 deletion core/api/dev/svix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ curl --silent -X 'GET' \
-H "Authorization: Bearer $SVIX_SECRET" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' | jq
```
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CallbackEndpointAddInput = GT.Input({
const CallbackEndpointAdd = GT.Field<
null,
GraphQLPublicContextAuth,
{ input: { url: string } }
{ input: { url: string | Error } }
>({
extensions: {
complexity: 120,
Expand All @@ -26,6 +26,9 @@ const CallbackEndpointAdd = GT.Field<
},
resolve: async (_, args, { domainAccount }: { domainAccount: Account }) => {
const { url } = args.input
if (url instanceof Error) {
return { errors: [{ message: url.message }] }
}

const result = await Callback.addEndpoint({
accountId: domainAccount.id,
Expand Down
26 changes: 22 additions & 4 deletions core/api/src/graphql/public/types/scalar/endpoint-url.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import { URL } from "url"

import { GT } from "@/graphql/index"
import { InputValidationError } from "@/graphql/error"

const EndpointUrl = GT.Scalar({
name: "EndpointUrl",
description: "Url that will be fetched on events for the account",
serialize(value) {
if (typeof value !== "string") {
return "Invalid value for EndpointUrl"
parseValue(value) {
if (typeof value === "string") {
return validUrlValue(value)
}
return value
return new InputValidationError({ message: "Invalid type for EndpointUrl" })
},
parseLiteral(ast) {
if (ast.kind === GT.Kind.STRING) {
return validUrlValue(ast.value)
}
return new InputValidationError({ message: "Invalid type for EndpointUrl" })
},
})

function validUrlValue(value: string) {
try {
new URL(value)
return value
} catch (error) {
return new InputValidationError({ message: "Invalid value for EndpointUrl" })
}
}

export default EndpointUrl
33 changes: 26 additions & 7 deletions core/api/src/services/svix/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { ApplicationIn, Svix } from "svix"

import { SvixError, UnknownSvixError } from "./errors"

import { baseLogger } from "@/services/logger"
import { InvalidUrlError } from "@/domain/callback/errors"
import { parseErrorMessageFromUnknown } from "@/domain/shared"

import {
addAttributesToCurrentSpan,
wrapAsyncFunctionsToRunInSpan,
} from "@/services/tracing"
import { baseLogger } from "@/services/logger"

function prefixObjectKeys(
obj: Record<string, string>,
Expand Down Expand Up @@ -56,7 +58,7 @@ export const CallbackService = (config: SvixConfig) => {
if ((err as SvixError).code === 409) {
// we create app on the fly, so we are expecting this error and can ignore it
} else {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}
}
Expand Down Expand Up @@ -91,7 +93,7 @@ export const CallbackService = (config: SvixConfig) => {
baseLogger.info({ res }, `message sent successfully to ${accountCallbackId}`)
return res
} catch (err) {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}

Expand All @@ -106,7 +108,7 @@ export const CallbackService = (config: SvixConfig) => {
const res = await svix.authentication.appPortalAccess(accountCallbackId, {})
return res
} catch (err) {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}

Expand All @@ -128,7 +130,7 @@ export const CallbackService = (config: SvixConfig) => {
})
return res
} catch (err) {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}

Expand All @@ -142,7 +144,7 @@ export const CallbackService = (config: SvixConfig) => {
const res = await svix.endpoint.list(accountCallbackId)
return res.data.map((endpoint) => ({ id: endpoint.id, url: endpoint.url }))
} catch (err) {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}

Expand All @@ -159,7 +161,7 @@ export const CallbackService = (config: SvixConfig) => {
await svix.endpoint.delete(accountCallbackId, endpointId)
return true
} catch (err) {
return new UnknownSvixError(err)
return handleCommonErrors(err)
}
}

Expand All @@ -168,3 +170,20 @@ export const CallbackService = (config: SvixConfig) => {
fns: { sendMessage, getWebsocketPortal, addEndpoint, listEndpoints, deleteEndpoint },
})
}

const handleCommonErrors = (err: Error | string | unknown) => {
const errMsg = parseErrorMessageFromUnknown(err)

const match = (knownErrDetail: RegExp): boolean => knownErrDetail.test(errMsg)

switch (true) {
case match(KnownSvixErrorMessages.InvalidHttpsUrl):
return new InvalidUrlError("URL must be https")

default:
return new UnknownSvixError(errMsg)
}
}
export const KnownSvixErrorMessages = {
InvalidHttpsUrl: /endpoint_https_only/,
} as const
Loading