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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more