diff --git a/packages/hoppscotch-common/src/helpers/auth/auth-types.ts b/packages/hoppscotch-common/src/helpers/auth/auth-types.ts new file mode 100644 index 00000000..d73c5735 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/auth-types.ts @@ -0,0 +1,92 @@ +import { + Environment, + HoppRESTAuth, + HoppRESTHeader, + HoppRESTParam, + HoppRESTRequest, +} from "@hoppscotch/data" +import { + generateApiKeyAuthHeaders, + generateApiKeyAuthParams, +} from "./types/api-key" +import { + generateAwsSignatureAuthHeaders, + generateAwsSignatureAuthParams, +} from "./types/aws-signature" +import { generateBasicAuthHeaders } from "./types/basic" +import { generateBearerAuthHeaders } from "./types/bearer" +import { generateDigestAuthHeaders } from "./types/digest" +import { generateHawkAuthHeaders } from "./types/hawk" +import { generateJwtAuthHeaders, generateJwtAuthParams } from "./types/jwt" +import { + generateOAuth2AuthHeaders, + generateOAuth2AuthParams, +} from "./types/oauth2" + +/** + * Generate headers for the given auth type using function-based approach + */ +export async function generateAuthHeaders( + auth: HoppRESTAuth, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + switch (auth.authType) { + case "basic": + return generateBasicAuthHeaders(auth, request, envVars, showKeyIfSecret) + case "bearer": + return generateBearerAuthHeaders(auth, request, envVars, showKeyIfSecret) + case "api-key": + return auth.addTo === "HEADERS" + ? generateApiKeyAuthHeaders(auth, request, envVars, showKeyIfSecret) + : [] + case "oauth-2": + return generateOAuth2AuthHeaders(auth, request, envVars, showKeyIfSecret) + case "digest": + return generateDigestAuthHeaders(auth, request, envVars, showKeyIfSecret) + case "aws-signature": + return generateAwsSignatureAuthHeaders( + auth, + request, + envVars, + showKeyIfSecret + ) + case "hawk": + return generateHawkAuthHeaders(auth, request, envVars, showKeyIfSecret) + case "jwt": + return generateJwtAuthHeaders(auth, request, envVars, showKeyIfSecret) + default: + return [] + } +} + +/** + * Generate query parameters for the given auth type using function-based approach + */ +export async function generateAuthParams( + auth: HoppRESTAuth, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + switch (auth.authType) { + case "api-key": + return auth.addTo === "QUERY_PARAMS" + ? generateApiKeyAuthParams(auth, request, envVars, showKeyIfSecret) + : [] + case "oauth-2": + return generateOAuth2AuthParams(auth, request, envVars, showKeyIfSecret) + case "aws-signature": + return generateAwsSignatureAuthParams( + auth, + request, + envVars, + showKeyIfSecret + ) + case "jwt": + return generateJwtAuthParams(auth, request, envVars, showKeyIfSecret) + default: + return [] + } +} diff --git a/packages/hoppscotch-common/src/helpers/auth/index.ts b/packages/hoppscotch-common/src/helpers/auth/index.ts index 976098c4..554711eb 100644 --- a/packages/hoppscotch-common/src/helpers/auth/index.ts +++ b/packages/hoppscotch-common/src/helpers/auth/index.ts @@ -19,7 +19,8 @@ export const replaceTemplateStringsInObjectValues = < ? restTabsService.currentActiveTab.value.document.request.requestVariables.map( ({ key, value }) => ({ key, - value, + initialValue: value, + currentValue: value, secret: false, }) ) diff --git a/packages/hoppscotch-common/src/helpers/auth/types/api-key.ts b/packages/hoppscotch-common/src/helpers/auth/types/api-key.ts new file mode 100644 index 00000000..d6c0d249 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/api-key.ts @@ -0,0 +1,49 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" + +export async function generateApiKeyAuthHeaders( + auth: HoppRESTAuth & { authType: "api-key" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "HEADERS") return [] + + return [ + { + active: true, + key: parseTemplateString(auth.key, envVars, false, showKeyIfSecret), + value: parseTemplateString( + auth.value ?? "", + envVars, + false, + showKeyIfSecret + ), + description: "", + }, + ] +} + +export async function generateApiKeyAuthParams( + auth: HoppRESTAuth & { authType: "api-key" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "QUERY_PARAMS") return [] + + return [ + { + active: true, + key: parseTemplateString(auth.key, envVars, false, showKeyIfSecret), + value: parseTemplateString(auth.value, envVars, false, showKeyIfSecret), + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/aws-signature.ts b/packages/hoppscotch-common/src/helpers/auth/types/aws-signature.ts new file mode 100644 index 00000000..8ebfefcb --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/aws-signature.ts @@ -0,0 +1,91 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" +import { AwsV4Signer } from "aws4fetch" +import { getFinalBodyFromRequest } from "~/helpers/utils/EffectiveURL" + +export async function generateAwsSignatureAuthHeaders( + auth: HoppRESTAuth & { authType: "aws-signature" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "HEADERS") return [] + + const currentDate = new Date() + const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "") + const { method, endpoint } = request + + const body = getFinalBodyFromRequest(request, envVars) + + const signer = new AwsV4Signer({ + method: method, + body: body?.toString(), + datetime: amzDate, + accessKeyId: parseTemplateString(auth.accessKey, envVars), + secretAccessKey: parseTemplateString(auth.secretKey, envVars), + region: parseTemplateString(auth.region, envVars) ?? "us-east-1", + service: parseTemplateString(auth.serviceName, envVars), + sessionToken: + auth.serviceToken && parseTemplateString(auth.serviceToken, envVars), + url: parseTemplateString(endpoint, envVars), + }) + + const sign = await signer.sign() + const headers: HoppRESTHeader[] = [] + + sign.headers.forEach((value, key) => { + headers.push({ + active: true, + key: key, + value: value, + description: "", + }) + }) + + return headers +} + +export async function generateAwsSignatureAuthParams( + auth: HoppRESTAuth & { authType: "aws-signature" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "QUERY_PARAMS") return [] + + const currentDate = new Date() + const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "") + + const signer = new AwsV4Signer({ + method: request.method, + datetime: amzDate, + signQuery: true, + accessKeyId: parseTemplateString(auth.accessKey, envVars), + secretAccessKey: parseTemplateString(auth.secretKey, envVars), + region: parseTemplateString(auth.region, envVars) ?? "us-east-1", + service: parseTemplateString(auth.serviceName, envVars), + sessionToken: + auth.serviceToken && parseTemplateString(auth.serviceToken, envVars), + url: parseTemplateString(request.endpoint, envVars), + }) + + const sign = await signer.sign() + const params: HoppRESTParam[] = [] + + for (const [key, value] of sign.url.searchParams) { + params.push({ + active: true, + key: key, + value: value, + description: "", + }) + } + + return params +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/basic.ts b/packages/hoppscotch-common/src/helpers/auth/types/basic.ts new file mode 100644 index 00000000..4359c963 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/basic.ts @@ -0,0 +1,37 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" + +export async function generateBasicAuthHeaders( + auth: HoppRESTAuth & { authType: "basic" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + const username = parseTemplateString( + auth.username, + envVars, + false, + showKeyIfSecret + ) + const password = parseTemplateString( + auth.password, + envVars, + false, + showKeyIfSecret + ) + + return [ + { + active: true, + key: "Authorization", + value: `Basic ${btoa(`${username}:${password}`)}`, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/bearer.ts b/packages/hoppscotch-common/src/helpers/auth/types/bearer.ts new file mode 100644 index 00000000..720c09dc --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/bearer.ts @@ -0,0 +1,26 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" + +export async function generateBearerAuthHeaders( + auth: HoppRESTAuth & { authType: "bearer" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + const token = parseTemplateString(auth.token, envVars, false, showKeyIfSecret) + + return [ + { + active: true, + key: "Authorization", + value: `Bearer ${token}`, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/digest.ts b/packages/hoppscotch-common/src/helpers/auth/types/digest.ts new file mode 100644 index 00000000..43aa6c06 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/digest.ts @@ -0,0 +1,66 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" +import { + DigestAuthParams, + fetchInitialDigestAuthInfo, + generateDigestAuthHeader, +} from "../digest" +import { getFinalBodyFromRequest } from "~/helpers/utils/EffectiveURL" + +export async function generateDigestAuthHeaders( + auth: HoppRESTAuth, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.authType !== "digest") return [] + + const { method, endpoint } = request + + // Step 1: Fetch the initial auth info (nonce, realm, etc.) + const authInfo = await fetchInitialDigestAuthInfo( + parseTemplateString(endpoint, envVars), + method + ) + + // Get the body content for digest calculation + const reqBody = getFinalBodyFromRequest(request, envVars, showKeyIfSecret) + + // Step 2: Set up the parameters for the digest authentication header + const digestAuthParams: DigestAuthParams = { + username: parseTemplateString(auth.username, envVars), + password: parseTemplateString(auth.password, envVars), + realm: auth.realm + ? parseTemplateString(auth.realm, envVars) + : authInfo.realm, + nonce: auth.nonce + ? parseTemplateString(auth.nonce, envVars) + : authInfo.nonce, + endpoint: parseTemplateString(endpoint, envVars), + method, + algorithm: auth.algorithm ?? authInfo.algorithm, + qop: auth.qop ? parseTemplateString(auth.qop, envVars) : authInfo.qop, + opaque: auth.opaque + ? parseTemplateString(auth.opaque, envVars) + : authInfo.opaque, + reqBody: typeof reqBody === "string" ? reqBody : "", + } + + // Step 3: Generate the Authorization header + const authHeaderValue = await generateDigestAuthHeader(digestAuthParams) + + return [ + { + active: true, + key: "Authorization", + value: authHeaderValue, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/hawk.ts b/packages/hoppscotch-common/src/helpers/auth/types/hawk.ts new file mode 100644 index 00000000..e02d37ea --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/hawk.ts @@ -0,0 +1,55 @@ +import { + parseTemplateString, + calculateHawkHeader, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" +import { getFinalBodyFromRequest } from "~/helpers/utils/EffectiveURL" + +export async function generateHawkAuthHeaders( + auth: HoppRESTAuth, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.authType !== "hawk") return [] + + const { method, endpoint, body } = request + + // Get the body content for payload hash calculation + const payload = getFinalBodyFromRequest(request, envVars, showKeyIfSecret) + + const hawkHeader = await calculateHawkHeader({ + url: parseTemplateString(endpoint, envVars), + method: method, + id: parseTemplateString(auth.authId, envVars), + key: parseTemplateString(auth.authKey, envVars), + algorithm: auth.algorithm, + + // Add content type and payload + contentType: body.contentType, + payload, + + // advanced parameters (optional) + includePayloadHash: auth.includePayloadHash, + nonce: auth.nonce ? parseTemplateString(auth.nonce, envVars) : undefined, + ext: auth.ext ? parseTemplateString(auth.ext, envVars) : undefined, + app: auth.app ? parseTemplateString(auth.app, envVars) : undefined, + dlg: auth.dlg ? parseTemplateString(auth.dlg, envVars) : undefined, + timestamp: auth.timestamp + ? parseInt(parseTemplateString(auth.timestamp, envVars), 10) + : undefined, + }) + + return [ + { + active: true, + key: "Authorization", + value: hawkHeader, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/jwt.ts b/packages/hoppscotch-common/src/helpers/auth/types/jwt.ts new file mode 100644 index 00000000..f9bcf0f3 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/jwt.ts @@ -0,0 +1,78 @@ +import { + parseTemplateString, + generateJWTToken, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" + +export async function generateJwtAuthHeaders( + auth: HoppRESTAuth & { authType: "jwt" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "HEADERS") return [] + + const token = await generateJWTToken({ + algorithm: auth.algorithm || "HS256", + secret: parseTemplateString(auth.secret, envVars, false), + privateKey: parseTemplateString(auth.privateKey, envVars, false), + payload: parseTemplateString(auth.payload, envVars, false), + jwtHeaders: parseTemplateString(auth.jwtHeaders, envVars, false), + isSecretBase64Encoded: auth.isSecretBase64Encoded, + }) + + if (!token) return [] + + // Get prefix (defaults to "Bearer " if not specified) + const headerPrefix = parseTemplateString( + auth.headerPrefix, + envVars, + false, + showKeyIfSecret + ) + + return [ + { + active: true, + key: "Authorization", + value: `${headerPrefix}${token}`, + description: "", + }, + ] +} + +export async function generateJwtAuthParams( + auth: HoppRESTAuth & { authType: "jwt" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "QUERY_PARAMS") return [] + + const token = await generateJWTToken({ + algorithm: auth.algorithm || "HS256", + secret: parseTemplateString(auth.secret, envVars, false), + privateKey: parseTemplateString(auth.privateKey, envVars, false), + payload: parseTemplateString(auth.payload, envVars, false), + jwtHeaders: parseTemplateString(auth.jwtHeaders, envVars, false), + isSecretBase64Encoded: auth.isSecretBase64Encoded, + }) + + if (!token) return [] + + // Get param name (defaults to "token" if not specified) + const paramName = parseTemplateString(auth.paramName, envVars) + + return [ + { + active: true, + key: paramName, + value: token, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/auth/types/oauth2.ts b/packages/hoppscotch-common/src/helpers/auth/types/oauth2.ts new file mode 100644 index 00000000..634b28dc --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/auth/types/oauth2.ts @@ -0,0 +1,58 @@ +import { + parseTemplateString, + HoppRESTAuth, + HoppRESTRequest, + Environment, + HoppRESTHeader, + HoppRESTParam, +} from "@hoppscotch/data" + +export async function generateOAuth2AuthHeaders( + auth: HoppRESTAuth & { authType: "oauth-2" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "HEADERS") return [] + + const token = parseTemplateString( + auth.grantTypeInfo.token, + envVars, + false, + showKeyIfSecret + ) + + return [ + { + active: true, + key: "Authorization", + value: `Bearer ${token}`, + description: "", + }, + ] +} + +export async function generateOAuth2AuthParams( + auth: HoppRESTAuth & { authType: "oauth-2" }, + request: HoppRESTRequest, + envVars: Environment["variables"], + showKeyIfSecret = false +): Promise { + if (auth.addTo !== "QUERY_PARAMS") return [] + + const token = parseTemplateString( + auth.grantTypeInfo.token, + envVars, + false, + showKeyIfSecret + ) + + return [ + { + active: true, + key: "access_token", + value: token, + description: "", + }, + ] +} diff --git a/packages/hoppscotch-common/src/helpers/utils/EffectiveURL.ts b/packages/hoppscotch-common/src/helpers/utils/EffectiveURL.ts index 4d657098..a92f9f6e 100644 --- a/packages/hoppscotch-common/src/helpers/utils/EffectiveURL.ts +++ b/packages/hoppscotch-common/src/helpers/utils/EffectiveURL.ts @@ -13,7 +13,6 @@ import { parseTemplateString, parseTemplateStringE, } from "@hoppscotch/data" -import { AwsV4Signer } from "aws4fetch" import * as A from "fp-ts/Array" import * as E from "fp-ts/Either" import { flow, pipe } from "fp-ts/function" @@ -29,12 +28,7 @@ import { toFormData } from "../functional/formData" import { tupleWithSameKeysToRecord } from "../functional/record" import { isJSONContentType } from "./contenttypes" import { stripComments } from "../editor/linting/jsonc" -import { - DigestAuthParams, - fetchInitialDigestAuthInfo, - generateDigestAuthHeader, -} from "../auth/digest" -import { calculateHawkHeader, generateJWTToken } from "@hoppscotch/data" +import { generateAuthHeaders, generateAuthParams } from "../auth/auth-types" export interface EffectiveHoppRESTRequest extends HoppRESTRequest { /** @@ -72,262 +66,14 @@ export const getComputedAuthHeaders = async ( ) => { const request = auth ? { auth: auth ?? { authActive: false } } : req - /** - * Handling Authorization header priority rules: - * - * 1. If a user-defined "Authorization" header exists in the request: - * a. We generally give it priority over auth-generated headers - * b. EXCEPTION: API Key auth that uses a different header name should still be included - * - * 2. We need to check both: - * - req.auth (the current request's auth settings) - * - auth param (possibly inherited auth from a parent collection) - * - * 3. Only return empty array (blocking auth headers) when: - * - Neither req.auth nor auth param is using API Key auth, OR - * - API Key auth is being used but specifically with the "Authorization" header name - * - This prevents API Key auth from being blocked when using custom header names - */ - if (req && req.headers.find((h) => h.key.toLowerCase() === "authorization")) { - // Only return empty array if not using API key auth or if API key is using "authorization" header - if ( - (!req.auth || - req.auth.authType !== "api-key" || - req.auth.key.toLowerCase() === "authorization") && - (!auth || - auth.authType !== "api-key" || - auth.key.toLowerCase() === "authorization") - ) { - return [] - } - } + if (!request || !request.auth || !request.auth.authActive) return [] - if (!request) return [] - - if (!request.auth || !request.auth.authActive) return [] - - const headers: HoppRESTHeader[] = [] - - // TODO: Support a better b64 implementation than btoa ? - if (request.auth.authType === "basic") { - const username = parse - ? parseTemplateString( - request.auth.username, - envVars, - false, - showKeyIfSecret - ) - : request.auth.username - const password = parse - ? parseTemplateString( - request.auth.password, - envVars, - false, - showKeyIfSecret - ) - : request.auth.password - - headers.push({ - active: true, - key: "Authorization", - value: `Basic ${btoa(`${username}:${password}`)}`, - description: "", - }) - } else if (request.auth.authType === "digest") { - const { method, endpoint } = request as HoppRESTRequest - - // Step 1: Fetch the initial auth info (nonce, realm, etc.) - const authInfo = await fetchInitialDigestAuthInfo( - parseTemplateString(endpoint, envVars), - method - ) - - const reqBody = getFinalBodyFromRequest( - req as HoppRESTRequest, - envVars, - showKeyIfSecret - ) - - // Step 2: Set up the parameters for the digest authentication header - const digestAuthParams: DigestAuthParams = { - username: parseTemplateString(request.auth.username, envVars), - password: parseTemplateString(request.auth.password, envVars), - realm: request.auth.realm - ? parseTemplateString(request.auth.realm, envVars) - : authInfo.realm, - nonce: request.auth.nonce - ? parseTemplateString(authInfo.nonce, envVars) - : authInfo.nonce, - endpoint: parseTemplateString(endpoint, envVars), - method, - algorithm: request.auth.algorithm ?? authInfo.algorithm, - qop: request.auth.qop - ? parseTemplateString(request.auth.qop, envVars) - : authInfo.qop, - opaque: request.auth.opaque - ? parseTemplateString(request.auth.opaque, envVars) - : authInfo.opaque, - reqBody: typeof reqBody === "string" ? reqBody : "", - } - - // Step 3: Generate the Authorization header - const authHeaderValue = await generateDigestAuthHeader(digestAuthParams) - - headers.push({ - active: true, - key: "Authorization", - value: authHeaderValue, - description: "", - }) - } else if ( - request.auth.authType === "bearer" || - (request.auth.authType === "oauth-2" && request.auth.addTo === "HEADERS") - ) { - const token = - request.auth.authType === "bearer" - ? request.auth.token - : request.auth.grantTypeInfo.token - - headers.push({ - active: true, - key: "Authorization", - value: `Bearer ${ - parse - ? parseTemplateString(token, envVars, false, showKeyIfSecret) - : token - }`, - description: "", - }) - } else if (request.auth.authType === "api-key") { - const { key, addTo } = request.auth - if (addTo === "HEADERS" && key) { - headers.push({ - active: true, - key: parseTemplateString(key, envVars, false, showKeyIfSecret), - value: parse - ? parseTemplateString( - request.auth.value ?? "", - envVars, - false, - showKeyIfSecret - ) - : (request.auth.value ?? ""), - description: "", - }) - } - } else if (request.auth.authType === "aws-signature") { - const { addTo } = request.auth - if (addTo === "HEADERS") { - const currentDate = new Date() - const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "") - const { method, endpoint } = req as HoppRESTRequest - - const body = getFinalBodyFromRequest(request, envVars) - - const signer = new AwsV4Signer({ - method: method, - body: body?.toString(), - datetime: amzDate, - accessKeyId: parseTemplateString(request.auth.accessKey, envVars), - secretAccessKey: parseTemplateString(request.auth.secretKey, envVars), - region: - parseTemplateString(request.auth.region, envVars) ?? "us-east-1", - service: parseTemplateString(request.auth.serviceName, envVars), - sessionToken: - request.auth.serviceToken && - parseTemplateString(request.auth.serviceToken, envVars), - url: parseTemplateString(endpoint, envVars), - }) - - const sign = await signer.sign() - - sign.headers.forEach((x, k) => { - headers.push({ - active: true, - key: k, - value: x, - description: "", - }) - }) - } - } else if (request.auth.authType === "hawk") { - const { method, endpoint, body } = req as HoppRESTRequest - - // Get the body content for payload hash calculation - const payload = getFinalBodyFromRequest( - req as HoppRESTRequest, - envVars, - showKeyIfSecret - ) - - const hawkHeader = await calculateHawkHeader({ - url: parseTemplateString(endpoint, envVars), // URL - method: method, // HTTP method - id: parseTemplateString(request.auth.authId, envVars), - key: parseTemplateString(request.auth.authKey, envVars), - algorithm: request.auth.algorithm, - - // Add content type and payload - contentType: body.contentType, - payload, - - // advanced parameters (optional) - includePayloadHash: request.auth.includePayloadHash, - nonce: request.auth.nonce - ? parseTemplateString(request.auth.nonce, envVars) - : undefined, - ext: request.auth.ext - ? parseTemplateString(request.auth.ext, envVars) - : undefined, - app: request.auth.app - ? parseTemplateString(request.auth.app, envVars) - : undefined, - dlg: request.auth.dlg - ? parseTemplateString(request.auth.dlg, envVars) - : undefined, - timestamp: request.auth.timestamp - ? parseInt(parseTemplateString(request.auth.timestamp, envVars), 10) - : undefined, - }) - - headers.push({ - active: true, - key: "Authorization", - value: hawkHeader, - description: "", - }) - } else if ( - request.auth.authType === "jwt" && - request.auth.addTo === "HEADERS" - ) { - const token = await generateJWTToken({ - algorithm: request.auth.algorithm || "HS256", - secret: parseTemplateString(request.auth.secret, envVars, false), - privateKey: parseTemplateString(request.auth.privateKey, envVars, false), - payload: parseTemplateString(request.auth.payload, envVars, false), - jwtHeaders: parseTemplateString(request.auth.jwtHeaders, envVars, false), - isSecretBase64Encoded: request.auth.isSecretBase64Encoded, - }) - - if (token) { - // Get prefix (defaults to "Bearer " if not specified) - const headerPrefix = parseTemplateString( - request.auth.headerPrefix, - envVars, - false, - showKeyIfSecret - ) - - headers.push({ - active: true, - key: "Authorization", - value: `${headerPrefix}${token}`, - description: "", - }) - } - } - - return headers + return await generateAuthHeaders( + request.auth, + req as HoppRESTRequest, + envVars, + showKeyIfSecret + ) } /** @@ -453,116 +199,10 @@ export const getComputedParams = async ( req: HoppRESTRequest, envVars: Environment["variables"] ): Promise => { - // When this gets complex, its best to split this function off (like with getComputedHeaders) - // API-key auth can be added to query params if (!req.auth || !req.auth.authActive) return [] - if ( - req.auth.authType !== "api-key" && - req.auth.authType !== "oauth-2" && - req.auth.authType !== "aws-signature" && - req.auth.authType !== "jwt" - ) - return [] - - if (req.auth.addTo !== "QUERY_PARAMS") return [] - - if (req.auth.authType === "aws-signature") { - const { addTo } = req.auth - const params: ComputedParam[] = [] - if (addTo === "QUERY_PARAMS") { - const currentDate = new Date() - const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "") - - const signer = new AwsV4Signer({ - method: req.method, - datetime: amzDate, - signQuery: true, - accessKeyId: parseTemplateString(req.auth.accessKey, envVars), - secretAccessKey: parseTemplateString(req.auth.secretKey, envVars), - region: parseTemplateString(req.auth.region, envVars) ?? "us-east-1", - service: parseTemplateString(req.auth.serviceName, envVars), - sessionToken: - req.auth.serviceToken && - parseTemplateString(req.auth.serviceToken, envVars), - url: parseTemplateString(req.endpoint, envVars), - }) - const sign = await signer.sign() - - for (const [k, v] of sign.url.searchParams) { - params.push({ - source: "auth" as const, - param: { - active: true, - key: k, - value: v, - description: "", - }, - }) - } - } - return params - } - - if (req.auth.authType === "api-key") { - return [ - { - source: "auth" as const, - param: { - active: true, - key: parseTemplateString(req.auth.key, envVars, false, true), - value: parseTemplateString(req.auth.value, envVars, false, true), - description: "", - }, - }, - ] - } - - if (req.auth.authType === "oauth-2") { - const { grantTypeInfo } = req.auth - return [ - { - source: "auth", - param: { - active: true, - key: "access_token", - value: parseTemplateString(grantTypeInfo.token, envVars), - description: "", - }, - }, - ] - } - - if (req.auth.authType === "jwt") { - const token = await generateJWTToken({ - algorithm: req.auth.algorithm || "HS256", - secret: parseTemplateString(req.auth.secret, envVars, false), - privateKey: parseTemplateString(req.auth.privateKey, envVars, false), - payload: parseTemplateString(req.auth.payload, envVars, false), - jwtHeaders: parseTemplateString(req.auth.jwtHeaders, envVars, false), - isSecretBase64Encoded: req.auth.isSecretBase64Encoded, - }) - - if (token) { - // Get param name (defaults to "token" if not specified) - const paramName = parseTemplateString(req.auth.paramName, envVars) - - return [ - { - source: "auth", - param: { - active: true, - key: paramName, - value: token, - description: "", - }, - }, - ] - } - return [] - } - - return [] + const params = await generateAuthParams(req.auth, req, envVars) + return params.map((param) => ({ source: "auth" as const, param })) } // Resolves environment variables in the body @@ -616,7 +256,7 @@ export const resolvesEnvsInBody = ( } } -function getFinalBodyFromRequest( +export function getFinalBodyFromRequest( request: HoppRESTRequest, envVariables: Environment["variables"], showKeyIfSecret = false diff --git a/packages/hoppscotch-common/type-check.mjs b/packages/hoppscotch-common/type-check.mjs index bc29b644..a9f90353 100644 --- a/packages/hoppscotch-common/type-check.mjs +++ b/packages/hoppscotch-common/type-check.mjs @@ -34,7 +34,10 @@ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) // Define the directory paths and file patterns to perform type checks on -const directoryPaths = [path.resolve(__dirname, "src", "services")] +const directoryPaths = [ + path.resolve(__dirname, "src", "services"), + path.resolve(__dirname, "src", "helpers", "auth"), +] const filePatterns = ["**/*.ts"] const tsConfigFileName = path.resolve(__dirname, "tsconfig.json")