Lambda authorizer
Using a basic authorization mechanism is better than nothing.
The Lambda authorizer is somewhat convoluted due to the particulars of how AWS' format works.
Here's the code:
code/Reservation/SlotReservation/src/infrastructure/authorizers/Authorizer.ts
1
import { APIGatewayProxyResult, AuthResponse } from "aws-lambda";
2
3
import fetch, { Response } from "node-fetch";
4
5
import { AuthorizationHeaderError } from "../../application/errors/AuthorizationHeaderError";
6
import { InvalidVerificationCodeError } from "../../application/errors/InvalidVerificationCodeError";
7
import { MissingSecurityApiEndpoint } from "../../application/errors/MissingSecurityApiEndpoint";
8
9
const SECURITY_API_ENDPOINT_VERIFY =
10
process.env.SECURITY_API_ENDPOINT_VERIFY || "";
11
12
/**
13
* @description Authorizer that will check the `event.Authorization` header
14
* for a slot ID (separated by a pound sign, or "hash tag") and a verification code
15
* and validate it against the Security API.
16
*
17
* @example `Authorization: b827bb85-7665-4c32-bb3c-25bca5d3cc48#abc123` header.
18
*/
19
export async function handler(event: EventInput): Promise<AuthResponse> {
20
try {
21
// @ts-ignore
22
if (event.httpMethod === "OPTIONS") return handleCors();
23
if (!SECURITY_API_ENDPOINT_VERIFY) throw new MissingSecurityApiEndpoint();
24
25
const { slotId, verificationCode } = getValues(event);
26
if (!slotId || !verificationCode) throw new AuthorizationHeaderError();
27
28
// Verify code
29
const isCodeValid = await validateCode(slotId, verificationCode);
30
if (!isCodeValid) throw new InvalidVerificationCodeError();
31
32
return generatePolicy(verificationCode, "Allow", event.methodArn, "");
33
} catch (error: any) {
34
console.error(error.message);
35
const { slotId } = getValues(event);
36
const id = slotId ? slotId : "UNKNOWN";
37
return generatePolicy(id, "Deny", event.methodArn, {});
38
}
39
}
40
41
/**
42
* @description Get required values
43
*/
44
function getValues(event: EventInput) {
45
const header = event.headers["Authorization"] || "";
46
const [slotId, verificationCode] = header.split("#");
47
return {
48
slotId,
49
verificationCode,
50
};
51
}
52
53
/**
54
* @description CORS handler.
55
*/
56
function handleCors() {
57
return {
58
statusCode: 200,
59
headers: {
60
"Access-Control-Allow-Headers": "Content-Type",
61
"Access-Control-Allow-Credentials": true,
62
"Access-Control-Allow-Origin": "*",
63
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
64
Vary: "Origin",
65
},
66
body: JSON.stringify("OK"),
67
} as APIGatewayProxyResult;
68
}
69
70
/**
71
* @description Creates the IAM policy for the response.
72
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
73
*/
74
const generatePolicy = (
75
principalId: string,
76
effect: string,
77
resource: string,
78
data: string | Record<string, any>
79
) => {
80
return {
81
principalId,
82
context: {
83
stringKey: JSON.stringify(data),
84
},
85
policyDocument: {
86
Version: "2012-10-17",
87
Statement: [
88
{
89
Action: "execute-api:Invoke",
90
Effect: effect,
91
Resource: resource,
92
},
93
],
94
},
95
};
96
};
97
98
/**
99
* @description Validate a code.
100
*/
101
async function validateCode(
102
slotId: string,
103
verificationCode: string
104
): Promise<boolean> {
105
return await fetch(SECURITY_API_ENDPOINT_VERIFY, {
106
body: JSON.stringify({
107
slotId,
108
code: verificationCode,
109
}),
110
method: "POST",
111
})
112
.then((response: Response) => response.json())
113
.then((result: boolean) => {
114
if (result === true) return true;
115
return false;
116
})
117
.catch((error: any) => {
118
console.error(error.message);
119
return false;
120
});
121
}
122
123
/**
124
* @description Very basic approximation of the
125
* required parts of the incoming event.
126
*/
127
type EventInput = {
128
headers: Record<string, string>;
129
httpMethod: "GET" | "POST" | "PATCH" | "OPTIONS";
130
methodArn: string;
131
};
Most of its contents are pure boilerplate that you can copy between projects to your heart's content.
The particulars in the handler are:
if (event.httpMethod === "OPTIONS") return handleCors();
if (!SECURITY_API_ENDPOINT_VERIFY) throw new MissingSecurityApiEndpoint();
const { slotId, verificationCode } = getValues(event);
if (!slotId || !verificationCode) throw new AuthorizationHeaderError();
// Verify code
const isCodeValid = await validateCode(slotId, verificationCode);
if (!isCodeValid) throw new InvalidVerificationCodeError();
return generatePolicy(verificationCode, "Allow", event.methodArn, "");
First of all, if this is a call from a source where CORS might be an issue we handle that case. Next, we ensure there is a constant set for our endpoint, or we throw an error. This one is serious if we hit this one, but at least we'll know it's a configuration issue and nothing else.
Then we see how the implementation expects an
Authorization
header to have a specific format with {SLOT_ID}#{VERIFICATION_CODE}
. Therefore we'll split by the hash, check their presence of them, and throw an error if either is missing.The actual validation then is nothing more than calling the endpoint that has been configured. If it is incorrect we return an error indicating this. If all is good, then we'll hit the positive branch of
generatePolicy()
.Last modified 8mo ago