Skip to content

Commit 965e4e1

Browse files
feat(common): authentication strategy improvements (hoppscotch#5130)
Co-authored-by: jamesgeorge007 <[email protected]>
1 parent 78a165d commit 965e4e1

File tree

12 files changed

+569
-373
lines changed

12 files changed

+569
-373
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {
2+
Environment,
3+
HoppRESTAuth,
4+
HoppRESTHeader,
5+
HoppRESTParam,
6+
HoppRESTRequest,
7+
} from "@hoppscotch/data"
8+
import {
9+
generateApiKeyAuthHeaders,
10+
generateApiKeyAuthParams,
11+
} from "./types/api-key"
12+
import {
13+
generateAwsSignatureAuthHeaders,
14+
generateAwsSignatureAuthParams,
15+
} from "./types/aws-signature"
16+
import { generateBasicAuthHeaders } from "./types/basic"
17+
import { generateBearerAuthHeaders } from "./types/bearer"
18+
import { generateDigestAuthHeaders } from "./types/digest"
19+
import { generateHawkAuthHeaders } from "./types/hawk"
20+
import { generateJwtAuthHeaders, generateJwtAuthParams } from "./types/jwt"
21+
import {
22+
generateOAuth2AuthHeaders,
23+
generateOAuth2AuthParams,
24+
} from "./types/oauth2"
25+
26+
/**
27+
* Generate headers for the given auth type using function-based approach
28+
*/
29+
export async function generateAuthHeaders(
30+
auth: HoppRESTAuth,
31+
request: HoppRESTRequest,
32+
envVars: Environment["variables"],
33+
showKeyIfSecret = false
34+
): Promise<HoppRESTHeader[]> {
35+
switch (auth.authType) {
36+
case "basic":
37+
return generateBasicAuthHeaders(auth, request, envVars, showKeyIfSecret)
38+
case "bearer":
39+
return generateBearerAuthHeaders(auth, request, envVars, showKeyIfSecret)
40+
case "api-key":
41+
return auth.addTo === "HEADERS"
42+
? generateApiKeyAuthHeaders(auth, request, envVars, showKeyIfSecret)
43+
: []
44+
case "oauth-2":
45+
return generateOAuth2AuthHeaders(auth, request, envVars, showKeyIfSecret)
46+
case "digest":
47+
return generateDigestAuthHeaders(auth, request, envVars, showKeyIfSecret)
48+
case "aws-signature":
49+
return generateAwsSignatureAuthHeaders(
50+
auth,
51+
request,
52+
envVars,
53+
showKeyIfSecret
54+
)
55+
case "hawk":
56+
return generateHawkAuthHeaders(auth, request, envVars, showKeyIfSecret)
57+
case "jwt":
58+
return generateJwtAuthHeaders(auth, request, envVars, showKeyIfSecret)
59+
default:
60+
return []
61+
}
62+
}
63+
64+
/**
65+
* Generate query parameters for the given auth type using function-based approach
66+
*/
67+
export async function generateAuthParams(
68+
auth: HoppRESTAuth,
69+
request: HoppRESTRequest,
70+
envVars: Environment["variables"],
71+
showKeyIfSecret = false
72+
): Promise<HoppRESTParam[]> {
73+
switch (auth.authType) {
74+
case "api-key":
75+
return auth.addTo === "QUERY_PARAMS"
76+
? generateApiKeyAuthParams(auth, request, envVars, showKeyIfSecret)
77+
: []
78+
case "oauth-2":
79+
return generateOAuth2AuthParams(auth, request, envVars, showKeyIfSecret)
80+
case "aws-signature":
81+
return generateAwsSignatureAuthParams(
82+
auth,
83+
request,
84+
envVars,
85+
showKeyIfSecret
86+
)
87+
case "jwt":
88+
return generateJwtAuthParams(auth, request, envVars, showKeyIfSecret)
89+
default:
90+
return []
91+
}
92+
}

packages/hoppscotch-common/src/helpers/auth/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export const replaceTemplateStringsInObjectValues = <
1919
? restTabsService.currentActiveTab.value.document.request.requestVariables.map(
2020
({ key, value }) => ({
2121
key,
22-
value,
22+
initialValue: value,
23+
currentValue: value,
2324
secret: false,
2425
})
2526
)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
parseTemplateString,
3+
HoppRESTAuth,
4+
HoppRESTRequest,
5+
Environment,
6+
HoppRESTHeader,
7+
HoppRESTParam,
8+
} from "@hoppscotch/data"
9+
10+
export async function generateApiKeyAuthHeaders(
11+
auth: HoppRESTAuth & { authType: "api-key" },
12+
request: HoppRESTRequest,
13+
envVars: Environment["variables"],
14+
showKeyIfSecret = false
15+
): Promise<HoppRESTHeader[]> {
16+
if (auth.addTo !== "HEADERS") return []
17+
18+
return [
19+
{
20+
active: true,
21+
key: parseTemplateString(auth.key, envVars, false, showKeyIfSecret),
22+
value: parseTemplateString(
23+
auth.value ?? "",
24+
envVars,
25+
false,
26+
showKeyIfSecret
27+
),
28+
description: "",
29+
},
30+
]
31+
}
32+
33+
export async function generateApiKeyAuthParams(
34+
auth: HoppRESTAuth & { authType: "api-key" },
35+
request: HoppRESTRequest,
36+
envVars: Environment["variables"],
37+
showKeyIfSecret = false
38+
): Promise<HoppRESTParam[]> {
39+
if (auth.addTo !== "QUERY_PARAMS") return []
40+
41+
return [
42+
{
43+
active: true,
44+
key: parseTemplateString(auth.key, envVars, false, showKeyIfSecret),
45+
value: parseTemplateString(auth.value, envVars, false, showKeyIfSecret),
46+
description: "",
47+
},
48+
]
49+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {
2+
parseTemplateString,
3+
HoppRESTAuth,
4+
HoppRESTRequest,
5+
Environment,
6+
HoppRESTHeader,
7+
HoppRESTParam,
8+
} from "@hoppscotch/data"
9+
import { AwsV4Signer } from "aws4fetch"
10+
import { getFinalBodyFromRequest } from "~/helpers/utils/EffectiveURL"
11+
12+
export async function generateAwsSignatureAuthHeaders(
13+
auth: HoppRESTAuth & { authType: "aws-signature" },
14+
request: HoppRESTRequest,
15+
envVars: Environment["variables"],
16+
showKeyIfSecret = false
17+
): Promise<HoppRESTHeader[]> {
18+
if (auth.addTo !== "HEADERS") return []
19+
20+
const currentDate = new Date()
21+
const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "")
22+
const { method, endpoint } = request
23+
24+
const body = getFinalBodyFromRequest(request, envVars)
25+
26+
const signer = new AwsV4Signer({
27+
method: method,
28+
body: body?.toString(),
29+
datetime: amzDate,
30+
accessKeyId: parseTemplateString(auth.accessKey, envVars),
31+
secretAccessKey: parseTemplateString(auth.secretKey, envVars),
32+
region: parseTemplateString(auth.region, envVars) ?? "us-east-1",
33+
service: parseTemplateString(auth.serviceName, envVars),
34+
sessionToken:
35+
auth.serviceToken && parseTemplateString(auth.serviceToken, envVars),
36+
url: parseTemplateString(endpoint, envVars),
37+
})
38+
39+
const sign = await signer.sign()
40+
const headers: HoppRESTHeader[] = []
41+
42+
sign.headers.forEach((value, key) => {
43+
headers.push({
44+
active: true,
45+
key: key,
46+
value: value,
47+
description: "",
48+
})
49+
})
50+
51+
return headers
52+
}
53+
54+
export async function generateAwsSignatureAuthParams(
55+
auth: HoppRESTAuth & { authType: "aws-signature" },
56+
request: HoppRESTRequest,
57+
envVars: Environment["variables"],
58+
showKeyIfSecret = false
59+
): Promise<HoppRESTParam[]> {
60+
if (auth.addTo !== "QUERY_PARAMS") return []
61+
62+
const currentDate = new Date()
63+
const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, "")
64+
65+
const signer = new AwsV4Signer({
66+
method: request.method,
67+
datetime: amzDate,
68+
signQuery: true,
69+
accessKeyId: parseTemplateString(auth.accessKey, envVars),
70+
secretAccessKey: parseTemplateString(auth.secretKey, envVars),
71+
region: parseTemplateString(auth.region, envVars) ?? "us-east-1",
72+
service: parseTemplateString(auth.serviceName, envVars),
73+
sessionToken:
74+
auth.serviceToken && parseTemplateString(auth.serviceToken, envVars),
75+
url: parseTemplateString(request.endpoint, envVars),
76+
})
77+
78+
const sign = await signer.sign()
79+
const params: HoppRESTParam[] = []
80+
81+
for (const [key, value] of sign.url.searchParams) {
82+
params.push({
83+
active: true,
84+
key: key,
85+
value: value,
86+
description: "",
87+
})
88+
}
89+
90+
return params
91+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
parseTemplateString,
3+
HoppRESTAuth,
4+
HoppRESTRequest,
5+
Environment,
6+
HoppRESTHeader,
7+
HoppRESTParam,
8+
} from "@hoppscotch/data"
9+
10+
export async function generateBasicAuthHeaders(
11+
auth: HoppRESTAuth & { authType: "basic" },
12+
request: HoppRESTRequest,
13+
envVars: Environment["variables"],
14+
showKeyIfSecret = false
15+
): Promise<HoppRESTHeader[]> {
16+
const username = parseTemplateString(
17+
auth.username,
18+
envVars,
19+
false,
20+
showKeyIfSecret
21+
)
22+
const password = parseTemplateString(
23+
auth.password,
24+
envVars,
25+
false,
26+
showKeyIfSecret
27+
)
28+
29+
return [
30+
{
31+
active: true,
32+
key: "Authorization",
33+
value: `Basic ${btoa(`${username}:${password}`)}`,
34+
description: "",
35+
},
36+
]
37+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
parseTemplateString,
3+
HoppRESTAuth,
4+
HoppRESTRequest,
5+
Environment,
6+
HoppRESTHeader,
7+
HoppRESTParam,
8+
} from "@hoppscotch/data"
9+
10+
export async function generateBearerAuthHeaders(
11+
auth: HoppRESTAuth & { authType: "bearer" },
12+
request: HoppRESTRequest,
13+
envVars: Environment["variables"],
14+
showKeyIfSecret = false
15+
): Promise<HoppRESTHeader[]> {
16+
const token = parseTemplateString(auth.token, envVars, false, showKeyIfSecret)
17+
18+
return [
19+
{
20+
active: true,
21+
key: "Authorization",
22+
value: `Bearer ${token}`,
23+
description: "",
24+
},
25+
]
26+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
parseTemplateString,
3+
HoppRESTAuth,
4+
HoppRESTRequest,
5+
Environment,
6+
HoppRESTHeader,
7+
HoppRESTParam,
8+
} from "@hoppscotch/data"
9+
import {
10+
DigestAuthParams,
11+
fetchInitialDigestAuthInfo,
12+
generateDigestAuthHeader,
13+
} from "../digest"
14+
import { getFinalBodyFromRequest } from "~/helpers/utils/EffectiveURL"
15+
16+
export async function generateDigestAuthHeaders(
17+
auth: HoppRESTAuth,
18+
request: HoppRESTRequest,
19+
envVars: Environment["variables"],
20+
showKeyIfSecret = false
21+
): Promise<HoppRESTHeader[]> {
22+
if (auth.authType !== "digest") return []
23+
24+
const { method, endpoint } = request
25+
26+
// Step 1: Fetch the initial auth info (nonce, realm, etc.)
27+
const authInfo = await fetchInitialDigestAuthInfo(
28+
parseTemplateString(endpoint, envVars),
29+
method
30+
)
31+
32+
// Get the body content for digest calculation
33+
const reqBody = getFinalBodyFromRequest(request, envVars, showKeyIfSecret)
34+
35+
// Step 2: Set up the parameters for the digest authentication header
36+
const digestAuthParams: DigestAuthParams = {
37+
username: parseTemplateString(auth.username, envVars),
38+
password: parseTemplateString(auth.password, envVars),
39+
realm: auth.realm
40+
? parseTemplateString(auth.realm, envVars)
41+
: authInfo.realm,
42+
nonce: auth.nonce
43+
? parseTemplateString(auth.nonce, envVars)
44+
: authInfo.nonce,
45+
endpoint: parseTemplateString(endpoint, envVars),
46+
method,
47+
algorithm: auth.algorithm ?? authInfo.algorithm,
48+
qop: auth.qop ? parseTemplateString(auth.qop, envVars) : authInfo.qop,
49+
opaque: auth.opaque
50+
? parseTemplateString(auth.opaque, envVars)
51+
: authInfo.opaque,
52+
reqBody: typeof reqBody === "string" ? reqBody : "",
53+
}
54+
55+
// Step 3: Generate the Authorization header
56+
const authHeaderValue = await generateDigestAuthHeader(digestAuthParams)
57+
58+
return [
59+
{
60+
active: true,
61+
key: "Authorization",
62+
value: authHeaderValue,
63+
description: "",
64+
},
65+
]
66+
}

0 commit comments

Comments
 (0)