91 lines
4 KiB
JavaScript
91 lines
4 KiB
JavaScript
|
|
// lambdas/auth/index.js
|
||
|
|
// Public (unauthenticated) endpoints:
|
||
|
|
// GET /auth/me — returns user profile from Google token (client calls after login)
|
||
|
|
// GET /auth/config — returns Google Client ID so the frontend can init Google Sign-In
|
||
|
|
// POST /auth/logout — clears server-side session record
|
||
|
|
|
||
|
|
const { OAuth2Client } = require("google-auth-library");
|
||
|
|
const { DynamoDBDocumentClient, DeleteCommand } = require("@aws-sdk/lib-dynamodb");
|
||
|
|
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
|
||
|
|
const { ok, unauthorized, serverError, CORS_HEADERS, parseBody } = require("@ledgerflow/shared");
|
||
|
|
|
||
|
|
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
|
||
|
|
const SESSIONS_TABLE = process.env.SESSIONS_TABLE || "ledgerflow-sessions";
|
||
|
|
|
||
|
|
const googleClient = new OAuth2Client(GOOGLE_CLIENT_ID);
|
||
|
|
const dynamo = DynamoDBDocumentClient.from(
|
||
|
|
new DynamoDBClient({ region: process.env.AWS_REGION || "us-east-1" })
|
||
|
|
);
|
||
|
|
|
||
|
|
exports.handler = async (event) => {
|
||
|
|
// CORS preflight
|
||
|
|
if (event.requestContext?.http?.method === "OPTIONS") {
|
||
|
|
return { statusCode: 200, headers: CORS_HEADERS, body: "" };
|
||
|
|
}
|
||
|
|
|
||
|
|
const method = event.requestContext?.http?.method || event.httpMethod;
|
||
|
|
const path = event.rawPath || event.path || "";
|
||
|
|
|
||
|
|
try {
|
||
|
|
// ── GET /auth/config ────────────────────────────────────────────────────
|
||
|
|
// Returns public config the frontend needs to initialize Google Sign-In
|
||
|
|
if (method === "GET" && path.endsWith("/config")) {
|
||
|
|
return ok({
|
||
|
|
googleClientId: GOOGLE_CLIENT_ID,
|
||
|
|
// The frontend redirects here after Google login
|
||
|
|
// Using Google's newer Identity Services (one-tap / button flow)
|
||
|
|
// No redirect URI needed — token is returned directly to the JS callback
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── GET /auth/me ────────────────────────────────────────────────────────
|
||
|
|
// Validates Bearer token and returns user profile
|
||
|
|
// Called immediately after Google Sign-In succeeds on the frontend
|
||
|
|
if (method === "GET" && path.endsWith("/me")) {
|
||
|
|
const authHeader = event.headers?.authorization || event.headers?.Authorization || "";
|
||
|
|
if (!authHeader.startsWith("Bearer ")) return unauthorized("Missing token");
|
||
|
|
|
||
|
|
const token = authHeader.slice(7);
|
||
|
|
const ticket = await googleClient.verifyIdToken({
|
||
|
|
idToken: token,
|
||
|
|
audience: GOOGLE_CLIENT_ID,
|
||
|
|
});
|
||
|
|
const payload = ticket.getPayload();
|
||
|
|
if (!payload) return unauthorized("Invalid token");
|
||
|
|
|
||
|
|
return ok({
|
||
|
|
userId: payload.sub,
|
||
|
|
email: payload.email,
|
||
|
|
name: payload.name,
|
||
|
|
picture: payload.picture,
|
||
|
|
domain: payload.hd || null, // Google Workspace hosted domain
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── POST /auth/logout ───────────────────────────────────────────────────
|
||
|
|
// Removes server-side session (best-effort)
|
||
|
|
if (method === "POST" && path.endsWith("/logout")) {
|
||
|
|
const authHeader = event.headers?.authorization || event.headers?.Authorization || "";
|
||
|
|
if (authHeader.startsWith("Bearer ")) {
|
||
|
|
const token = authHeader.slice(7);
|
||
|
|
try {
|
||
|
|
const ticket = await googleClient.verifyIdToken({ idToken: token, audience: GOOGLE_CLIENT_ID });
|
||
|
|
const payload = ticket.getPayload();
|
||
|
|
if (payload?.sub) {
|
||
|
|
await dynamo.send(new DeleteCommand({ TableName: SESSIONS_TABLE, Key: { userId: payload.sub } }));
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// Best-effort; token may already be expired
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ok({ message: "Logged out" });
|
||
|
|
}
|
||
|
|
|
||
|
|
return { statusCode: 404, headers: CORS_HEADERS, body: JSON.stringify({ error: "Not Found" }) };
|
||
|
|
|
||
|
|
} catch (err) {
|
||
|
|
console.error("[AUTH]", err);
|
||
|
|
return serverError("Auth error", err);
|
||
|
|
}
|
||
|
|
};
|