feat(common): authentication strategy improvements (#5130)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
78a165dcbb
commit
965e4e1d44
12 changed files with 569 additions and 373 deletions
92
packages/hoppscotch-common/src/helpers/auth/auth-types.ts
Normal file
92
packages/hoppscotch-common/src/helpers/auth/auth-types.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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<HoppRESTParam[]> {
|
||||
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 []
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,8 @@ export const replaceTemplateStringsInObjectValues = <
|
|||
? restTabsService.currentActiveTab.value.document.request.requestVariables.map(
|
||||
({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
initialValue: value,
|
||||
currentValue: value,
|
||||
secret: false,
|
||||
})
|
||||
)
|
||||
|
|
|
|||
49
packages/hoppscotch-common/src/helpers/auth/types/api-key.ts
Normal file
49
packages/hoppscotch-common/src/helpers/auth/types/api-key.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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<HoppRESTParam[]> {
|
||||
if (auth.addTo !== "QUERY_PARAMS") return []
|
||||
|
||||
return [
|
||||
{
|
||||
active: true,
|
||||
key: parseTemplateString(auth.key, envVars, false, showKeyIfSecret),
|
||||
value: parseTemplateString(auth.value, envVars, false, showKeyIfSecret),
|
||||
description: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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<HoppRESTParam[]> {
|
||||
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
|
||||
}
|
||||
37
packages/hoppscotch-common/src/helpers/auth/types/basic.ts
Normal file
37
packages/hoppscotch-common/src/helpers/auth/types/basic.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
26
packages/hoppscotch-common/src/helpers/auth/types/bearer.ts
Normal file
26
packages/hoppscotch-common/src/helpers/auth/types/bearer.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
const token = parseTemplateString(auth.token, envVars, false, showKeyIfSecret)
|
||||
|
||||
return [
|
||||
{
|
||||
active: true,
|
||||
key: "Authorization",
|
||||
value: `Bearer ${token}`,
|
||||
description: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
66
packages/hoppscotch-common/src/helpers/auth/types/digest.ts
Normal file
66
packages/hoppscotch-common/src/helpers/auth/types/digest.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
55
packages/hoppscotch-common/src/helpers/auth/types/hawk.ts
Normal file
55
packages/hoppscotch-common/src/helpers/auth/types/hawk.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
78
packages/hoppscotch-common/src/helpers/auth/types/jwt.ts
Normal file
78
packages/hoppscotch-common/src/helpers/auth/types/jwt.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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<HoppRESTParam[]> {
|
||||
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: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
58
packages/hoppscotch-common/src/helpers/auth/types/oauth2.ts
Normal file
58
packages/hoppscotch-common/src/helpers/auth/types/oauth2.ts
Normal file
|
|
@ -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<HoppRESTHeader[]> {
|
||||
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<HoppRESTParam[]> {
|
||||
if (auth.addTo !== "QUERY_PARAMS") return []
|
||||
|
||||
const token = parseTemplateString(
|
||||
auth.grantTypeInfo.token,
|
||||
envVars,
|
||||
false,
|
||||
showKeyIfSecret
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
active: true,
|
||||
key: "access_token",
|
||||
value: token,
|
||||
description: "",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -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<ComputedParam[]> => {
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue