Published
Edited
Jul 11, 2022
2 forks
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
user
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
verifyIdToken(firebase, await user.getIdToken())
Insert cell
apiServer = {
const app = Router();
// Common to all routes
app.use(async (req, res, next) => {
// Security
try {
req.auth = await verifyIdToken(firebase, req.headers["idtoken"]);
next();
} catch (err) {
res.status(403).send("Auth: " + err.message);
}
});

app.get("/", (req, res) => {
res.status(200).end();
});

// Additional security checks for subdomain ownership and preparing a secret client.
app.all("/subdomains/:subdomain/secrets**", async (req, res, next) => {
const service_account =
req.context.secrets["endpointservices_secretadmin_service_account_key"];
const access_token = await getAccessTokenFromServiceAccount(
service_account
);
await signinWithAccessToken(firebase, access_token);

if (req.auth.uid.startsWith("observablehq|")) {
if (req.auth["observablehq.com"][req.params.subdomain] !== "admin") {
res.status(403).send("Not owner of subdomain");
}
} else {
const ownersQuery = await firebase
.firestore()
.collection("/services/ownership/owners")
.where("subdomain", "==", req.params.subdomain)
.where("uid", "==", req.auth.uid)
.limit(1)
.get();

if (ownersQuery.empty) {
res.status(403).send("Not owner of subdomain");
}
}

req.secretManagerClient = new SecretManagerServiceClient({
projectId: firebaseConfig.projectId,
access_token: access_token
});

next();
});

app.get("/subdomains/:subdomain/secrets", async (req, res) => {
try {
// This won't scale but we have no simple way of searching
// https://stackoverflow.com/questions/65044519/is-there-a-way-of-querying-google-secrets-manager
const secrets = (
await req.secretManagerClient.listSecrets({
parent: "projects/1986724398",
pageSize: 25000
})
)[0];

// Only return results prefixed by the subdomain the user has permission to
const results = secrets
.map((s) => ({ name: s.name.split("/")[3] }))
.filter((secret) => secret.name.startsWith(`${req.params.subdomain}_`));
res.json(results);
} catch (err) {
res.status(500).send(err.message);
}
});

app.put("/subdomains/:subdomain/secrets/:secret", async (req, res) => {
try {
// This won't scale but we have no simple way of searching
// https://stackoverflow.com/questions/65044519/is-there-a-way-of-querying-google-secrets-manager
const secretId = req.params.secret;
if (!secretId.startsWith(req.params.subdomain + "_")) {
res.status(400).send("secret name must be prefixed by subdomain");
}
const value = JSON.parse(req.body);

try {
// It might already been created in which case we really just want to create a new version
const secret = (
await req.secretManagerClient.createSecret({
parent: "projects/1986724398",
secretId,
secret: {
labels: { subdomain: req.params.subdomain },
replication: { automatic: {} }
}
})
)[0];
} catch (err) {
// It's ok to fail this one
}
const version = await req.secretManagerClient.addSecretVersion({
parent: `projects/1986724398/secrets/${secretId}`,
payload: {
data: value
}
});

// TODO, we should delete the previous version too

res.status(204).end();
} catch (err) {
res.status(500).send(err.message);
}
});

app.delete("/subdomains/:subdomain/secrets/:secret", async (req, res) => {
try {
// This won't scale but we have no simple way of searching
// https://stackoverflow.com/questions/65044519/is-there-a-way-of-querying-google-secrets-manager
const secretId = req.params.secret;
if (!secretId.startsWith(req.params.subdomain + "_")) {
res.status(400).send("secret name must be prefixed by subdomain");
}

await req.secretManagerClient.deleteSecret({
name: `projects/1986724398/secrets/${secretId}`
});

res.status(204).end();
} catch (err) {
res.status(500).send(err.message);
}
});

app.use((req, res) => {
res
.status(404)
.send(
"No route found in the https://observablehq.com/@endpointservices/secrets API"
);
});
return app;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
userModern
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more