Permissions
There are three privacy settings for a Val:
Public
- Discoverable and visible to everyone on the Val.town website
- Anyone can view the code and import it into their own vals
- HTTP endpoints are accessible to anyone
Unlisted
- Only visible to people who have the direct link
- Won’t appear in search results or public listings
- Anyone with the link can view the code and import it
- HTTP endpoints are accessible to anyone who knows the URL
Private
- Only visible to you
- Only your vals can import the code
- HTTP endpoints are accessible to anyone who knows the URL
Exposing your vals to the internet
HTTP endpoints are accessible to anyone who knows the URL, regardless of the val’s privacy setting. If your endpoint handles sensitive data, you should add authentication.
Since anyone can call your endpoints, if they interact with some data that should only be changed by yourself, you will need to make sure that those endpoints check for some kind of secret that only you know.
Here’s an example of a val exposed using the HTTP Val, secured with an environment variable that only I know:
export const secretEndpoint = (req: Request) => { const secretHeader = req.headers.get("Authorization");
if (secretHeader !== Deno.env.get("supersecrettoken")) { return new Response("Unauthorized", { status: 401 }); }
return new Response("My deepest darkest secret");};
If I called it without supplying the environment variable, I’d be denied access:
import { fetch } from "https://esm.town/v/std/fetch";
const response = await fetch("https://user-secretEndpoint.web.val.run");console.log(response);
Response { body: ReadableStream { locked: false }, bodyUsed: false, headers: Headers { /* omitted */ }, ok: false, redirected: false, status: 401, statusText: "Unauthorized", url: "http://localhost:3001/v1/fetch?url=https%3A%2F%2Fuser-secretEndpoint.web.val.run"}
By supplying the environment variable in a header, I’m allowed access:
import { fetch } from "https://esm.town/v/std/fetch";
const response = await fetch("https://user-secretEndpoint.web.val.run", { headers: { Authorization: "birdsarentreal" },});console.log(response);
Response { body: ReadableStream { locked: false }, bodyUsed: false, headers: Headers { /* omitted */ }, ok: true, redirected: false, status: 200, statusText: "OK", url: "http://localhost:3001/v1/fetch?url=https%3A%2F%2Fuser-secretEndpoint.web.val.run"}
Public code referencing private data
It is safe for a a public val to reference one of your private vals or one of your environment variables. Private vals are like environment variables in this way — others can see that they’re being used, but not their values.
For example, I created a private val, example3
. You won’t be able to see or
reference example3
but I can use it in example4
which is public.
import { example3 } from "https://esm.town/v/user/example3";console.log("Hi,", example3);
Hi, User
You can infer that the value of example3
is "User"
because of
how it’s used here. This is why you have to be careful about publishing vals
that reference private data. Typically you will reference private data in a way
that makes it impossible for others to infer what it is, like you would with an
environment variable credentials. Below I am passing my environment variables to an Upstash
Redis store. You can see that I’m using these environment variables and the output of this
computation, but you can’t get those values, nor can you rerun this script with
my environment variables.
import { Redis } from "npm:@upstash/redis";const redis = new Redis({ url: Deno.env.get("upstashURL"), token: Deno.env.get("upstashToken"),});await redis.set("json-ex-1", { a: { b: "nested json" } });const get = await redis.get("json-ex-1");console.log(get);
nested json
Using another’s vals as a library
You can import any Public or Unlisted vals and use them in your own code. In this way it is safe to pass other’s code your private data and environment variables.
import { gpt3 } from "https://esm.town/v/patrickjm/gpt3?v=4";
export let librarySecret = gpt3({ prompt: "what is the meaning of life?", openAiKey: Deno.env.get("openai"),});
Custom Authentication
You can roll arbitrary authentication schemes in user space. For example, I find
it useful to simply supply a custom auth
header from my Clerk webhooks that I
check like a password against a value I store in my environment variables:
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
// # New Val Town User (on Clerk) -> Val Town Discord notification// Translates one kind of webhook (Clerk) into another (Discord)export async function handleDiscordNewUser(req: express.Request, res) { // check custom auth secret sent from clerk if (req.get("auth") !== process.env.clerkNonSensitive) return res.end("Unauthorized"); await discordWebhook({ url: Deno.env.get("discordUserEvents"), content: req.body.data.email_addresses[0].email_address + " " + req.body.data.profile_image_url, }); res.end("Success");}
I call this value clerkNonSensitive
because this value doesn’t protect any
data. It merely makes it impossible for anyone to trigger this public API
endpoint without the password. The worst that could happen if this password
leaks is that our team temporarily gets spam discord messages. Then I could just
change the password to a new value. For more sensitive use-cases, you’ll want to
sign & possibly encrypt the conveyed data using standard authentication methods.
Legacy Private Vals
Prior to February 12th, 2024, private HTTP vals required an API token to access their endpoints. The endpoint was only accessible to the val owner and wasn’t visible on the Val Town website.
This behavior was changed - HTTP endpoints are now accessible to anyone who knows the URL, regardless of privacy setting. Private vals created before this date have been given a Legacy private
permission setting to preserve their original behavior.