Skip to content
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
34 changes: 34 additions & 0 deletions lambda/authorizer-v2/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// API Gateway HTTP API Lambda authorizer (REQUEST type, simple responses,
// payload format 2.0) for the /lad_v2/... routes. Centralizes the Beacon
// token check that used to be duplicated in each of the five lad_v2
// Lambdas — same trusted-issuer allow-list, same JWKS verification, same
// required scope, via the shared verifyBeaconToken.mjs (see TRUSTED_ISS env
// var to add/remove issuers).
//
// On success, `sub` (the Beacon member id) is returned in `context`, which
// API Gateway forwards to the backend Lambda at
// event.requestContext.authorizer.lambda.sub — so downstream handlers don't
// need the raw token to know who's calling.
import { verifyBeaconToken } from './verifyBeaconToken.mjs';

export const handler = async (event) => {
const authHeader = event.headers?.authorization || event.headers?.Authorization;

try {
const claims = await verifyBeaconToken(authHeader);
return {
isAuthorized: true,
context: {
sub: String(claims.sub || claims.client_id || 'unknown'),
},
};
} catch (err) {
console.log(JSON.stringify({
msg: 'beacon_auth_denied',
fn: 'authorizer-v2',
path: event.rawPath,
error: err?.message || String(err),
}));
return { isAuthorized: false };
}
};
13 changes: 5 additions & 8 deletions lambda/default-assets-v2/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import crypto from 'crypto';
import { verifyBeaconToken } from './verifyBeaconToken.mjs';

// ── Config ──────────────────────────────────────────────────────────
const BUCKET = process.env.BUCKET_NAME || 'lighthouse-default-assets';
Expand Down Expand Up @@ -98,13 +97,11 @@ export const handler = async (event) => {
return respond(204, '');
}

let claims;
try {
claims = await verifyBeaconToken(event.headers?.authorization || event.headers?.Authorization);
} catch (err) {
return respond(401, { error: 'Unauthorized', message: err?.message || String(err) });
}
console.log(JSON.stringify({ msg: 'beacon_auth', fn: 'default-assets-v2', userId: claims.sub || claims.client_id || 'unknown', method }));
// Auth is enforced by the LH-BeaconAuthorizerV2 API Gateway authorizer
// before this handler is ever invoked; `sub` is the verified Beacon
// member id it passes through.
const userId = event.requestContext?.authorizer?.lambda?.sub || 'unknown';
console.log(JSON.stringify({ msg: 'beacon_auth', fn: 'default-assets-v2', userId, method }));

try {
// ---------- GET: Bulk fetch for a list of team IDs ----------
Expand Down
13 changes: 5 additions & 8 deletions lambda/geocode-v2/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import pkg from "@aws-sdk/client-geo-places";
import { verifyBeaconToken } from "./verifyBeaconToken.mjs";

const { GeoPlacesClient, GeocodeCommand, ReverseGeocodeCommand } = pkg;

Expand All @@ -26,13 +25,11 @@ export const handler = async (event) => {
return { statusCode: 204, headers: corsHeaders, body: "" };
}

let claims;
try {
claims = await verifyBeaconToken(event.headers?.authorization || event.headers?.Authorization);
} catch (err) {
return json(401, { error: "Unauthorized", message: err?.message || String(err) });
}
console.log(JSON.stringify({ msg: "beacon_auth", fn: "geocode-v2", userId: claims.sub || claims.client_id || "unknown" }));
// Auth is enforced by the LH-BeaconAuthorizerV2 API Gateway authorizer
// before this handler is ever invoked; `sub` is the verified Beacon
// member id it passes through.
const userId = event.requestContext?.authorizer?.lambda?.sub || "unknown";
console.log(JSON.stringify({ msg: "beacon_auth", fn: "geocode-v2", userId }));

const qsp = event?.queryStringParameters || {};

Expand Down
29 changes: 12 additions & 17 deletions lambda/map-layers-v2/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const { json, serverError } = require('./lib/response');
const { verifyBeaconToken } = require('./verifyBeaconToken');
const listLayers = require('./handlers/listLayers');
const createLayer = require('./handlers/createLayer');
const getLayer = require('./handlers/getLayer');
Expand All @@ -18,8 +17,8 @@ const updateLayerAttachment = require('./handlers/updateLayerAttachment');
// event.routeKey, which API Gateway sets to "<METHOD> <route path>" for
// whichever route matched (e.g. "GET /lad_v2/map-layers/{id}"). Every route
// except OPTIONS requires a valid `Authorization: Bearer <Beacon token>`
// header, verified against SES's identity server (see
// ./verifyBeaconToken.js).
// header — enforced by the LH-BeaconAuthorizerV2 API Gateway authorizer
// before this Lambda is ever invoked (see lambda/authorizer-v2).
const ROUTES = {
'GET /lad_v2/map-layers': listLayers,
'POST /lad_v2/map-layers': createLayer,
Expand All @@ -45,22 +44,18 @@ exports.handler = async (event) => {
return json(404, { error: 'Not found', routeKey });
}

let claims;
try {
claims = await verifyBeaconToken(event.headers?.authorization || event.headers?.Authorization);
} catch (err) {
return json(401, { error: 'Unauthorized', message: err?.message || String(err) });
}
console.log(JSON.stringify({ msg: 'beacon_auth', fn: 'map-layers-v2', userId: claims.sub || claims.client_id || 'unknown', route: routeKey }));
// `sub` is the verified Beacon member id, passed through from the
// LH-BeaconAuthorizerV2 authorizer's context.
const userId = event.requestContext?.authorizer?.lambda?.sub || 'unknown';
console.log(JSON.stringify({ msg: 'beacon_auth', fn: 'map-layers-v2', userId, route: routeKey }));

try {
// `claims` (the verified token payload) is passed through so
// permission-sensitive handlers (createLayer, upsertFeature,
// deleteFeature, deleteLayer) can authorize against claims.sub -- the
// Beacon member id, tamper-proof since it comes from a signature-
// verified JWT -- rather than any client-supplied actorId field, which
// a caller could set to whatever it wants.
return await handler(event, claims);
// `claims` is passed through so permission-sensitive handlers
// (createLayer, upsertFeature, deleteFeature, deleteLayer) can
// authorize against claims.sub -- tamper-proof since it comes from the
// gateway's signature-verified JWT -- rather than any client-supplied
// actorId field, which a caller could set to whatever it wants.
return await handler(event, { sub: userId });
} catch (err) {
console.error('map-layers-v2 handler error:', err, JSON.stringify({ routeKey }));
return serverError();
Expand Down
13 changes: 5 additions & 8 deletions lambda/route-v2/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GeoRoutesClient, CalculateRoutesCommand } from "@aws-sdk/client-geo-routes";
import { verifyBeaconToken } from "./verifyBeaconToken.mjs";

const client = new GeoRoutesClient({});

Expand Down Expand Up @@ -33,13 +32,11 @@ export const handler = async (event) => {
return { statusCode: 204, headers: CORS_HEADERS, body: "" };
}

let claims;
try {
claims = await verifyBeaconToken(event.headers?.authorization || event.headers?.Authorization);
} catch (err) {
return json(401, { error: "Unauthorized", message: err?.message || String(err) });
}
console.log(JSON.stringify({ msg: "beacon_auth", fn: "route-v2", userId: claims.sub || claims.client_id || "unknown" }));
// Auth is enforced by the LH-BeaconAuthorizerV2 API Gateway authorizer
// before this handler is ever invoked; `sub` is the verified Beacon
// member id it passes through.
const userId = event.requestContext?.authorizer?.lambda?.sub || "unknown";
console.log(JSON.stringify({ msg: "beacon_auth", fn: "route-v2", userId }));

let body;
try {
Expand Down
18 changes: 5 additions & 13 deletions lambda/share-v2/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
PutObjectCommand,
GetObjectCommand
} from "@aws-sdk/client-s3";
import { verifyBeaconToken } from "./verifyBeaconToken.mjs";

const s3 = new S3Client({});
const BUCKET_NAME = process.env.BUCKET_NAME;
const CONFIG_PREFIX = process.env.CONFIG_PREFIX || "";
Expand Down Expand Up @@ -33,17 +31,11 @@ export const handler = async (event) => {
};
}

let claims;
try {
claims = await verifyBeaconToken(event.headers?.authorization || event.headers?.Authorization);
} catch (err) {
return {
statusCode: 401,
headers: corsHeaders(),
body: JSON.stringify({ message: "Unauthorized", error: err?.message || String(err) })
};
}
console.log(JSON.stringify({ msg: "beacon_auth", fn: "share-v2", userId: claims.sub || claims.client_id || "unknown", method }));
// Auth is enforced by the LH-BeaconAuthorizerV2 API Gateway authorizer
// before this handler is ever invoked; `sub` is the verified Beacon
// member id it passes through.
const userId = event.requestContext?.authorizer?.lambda?.sub || "unknown";
console.log(JSON.stringify({ msg: "beacon_auth", fn: "share-v2", userId, method }));

if (method === "POST" && !query.id) {
return await handleCreateConfig(rawBody);
Expand Down