restricted-access
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRestricted App Access
受限应用访问
A val has two independent access settings. Changing one does not change the other:
- Code (:
privacy/public/unlisted) — who can read the source on val.town.private - App access (:
httpPrivacy/public) — who can call the val's HTTP endpoints.restricted
A val can have private code and a wide-open endpoint, or public code and a locked-down endpoint. 's field only moves the first one; app access is changed with .
update_valprivacyset_http_privacyRestricted app access is available to organizations that have the feature enabled. Vals created in such an org may default to — always read off a , , , or response rather than assuming a new val's URL is open.
restrictedhttpPrivacyget_val_detaillist_valscreate_valremix_valval有两个独立的访问设置,修改其中一个不会影响另一个:
- 代码(:
privacy/public/unlisted)——谁可以在val.town上查看源代码。private - 应用访问(:
httpPrivacy/public)——谁可以调用val的HTTP端点。restricted
一个val可以同时设置为私有代码+公开端点,或是公开代码+受限端点。的字段仅修改代码访问权限;应用访问权限需要通过修改。
update_valprivacyset_http_privacy已启用该功能的组织可以使用受限应用访问。在这类组织中创建的val可能默认为状态——请始终从、、或的响应中读取的值,不要默认新val的URL是公开可访问的。
restrictedget_val_detaillist_valscreate_valremix_valhttpPrivacyIs this the right tool?
这是合适的工具吗?
Two different things both sound like "make my app require a login":
- Restricted app access (this skill) gates the endpoint at the platform edge, before your code runs. Requests from people without access never reach the val. You write no auth code. Access is granted to whole organizations, not to individuals.
- (see the
std/oauthskill) runs inside your val: you wrap your handler, and anyone with a Val Town account can log in. You control the session and can build per-user features.oauth
Pick restricted access for an internal tool that only your team should reach. Pick when any Val Town user may sign in and the app needs its own notion of a logged-in user.
std/oauthDon't stack them by accident. Adding to an already-restricted val means the visitor authenticates twice — once at the gate, once in your code. If a restricted val needs to know who is viewing, use the identity header below instead of adding OAuth.
oauthMiddleware有两种不同的方案听起来都像是「让我的应用需要登录」:
- 受限应用访问(本技能)在平台边缘对端点进行防护,在你的代码运行前就完成校验。没有访问权限的用户的请求永远不会到达val。你不需要编写任何鉴权代码。访问权限会授予整个组织,而非单个用户。
- (参见
std/oauth技能)运行在你的val内部:你需要封装自己的处理函数,任何拥有Val Town账户的用户都可以登录。你可以控制会话,构建面向单个用户的功能。oauth
如果是仅团队可访问的内部工具,请选择受限访问。如果允许任何Val Town用户登录,且应用需要维护自己的登录用户体系,请选择。
std/oauth不要意外地叠加使用这两种方案。给已经受限的val添加会导致访问者需要进行两次身份验证——一次在平台网关,一次在你的代码中。如果受限val需要知道谁正在访问,请使用下文介绍的身份标头,而不是添加OAuth。
oauthMiddlewareWho gets through
谁可以通过访问校验
Access is granted to organizations, not individual people. A viewer gets in when the val has a grant to an org and that viewer is a member of it. Removing either one revokes access on the very next request — nothing is cached for the length of a session.
Grants come from:
- Direct grants — grants an org,
add_allowed_usershows current grants,list_allowed_usersrevokes one.remove_allowed_user - Domain rules — a val can admit everyone with an email address at a given domain. These appear in alongside direct grants.
list_allowed_users - Invitations — someone outside a granted org can be invited by email and gains access when they accept.
- Bypass tokens — a project-scoped secret for machines; see below.
访问权限会授予组织,而非个人。只有当val授予了某个组织访问权且访问者是该组织的成员时,访问者才能进入。只要移除其中任意一个条件,下一次请求就会被拒绝——不会在会话周期内缓存任何权限状态。
授权来源包括:
- 直接授权 —— 通过授予组织访问权,
add_allowed_user查看当前授权列表,list_allowed_users撤销授权。remove_allowed_user - 域名规则 —— val可以允许所有使用指定域名邮箱的用户访问。这类授权会和直接授权一起显示在的结果中。
list_allowed_users - 邀请 —— 被授权组织之外的用户可以通过邮件收到邀请,接受邀请后即可获得访问权限。
- 绕过令牌 —— 面向机器的项目级密钥;详见下文。
What everyone else sees
其他用户会看到什么
An unauthenticated request does not reach the val. The platform answers with a redirect to a Val Town login or authorization page. This is the single most common source of confusion when debugging a restricted val:
302- reports a redirect it won't follow.
fetch_val_endpoint - shows a
curlto302instead of your response.val.town - An API client gets HTML from a login page where it expected JSON.
- A visitor without access who is logged in gets a explaining they need access to their organization.
403
None of these mean the val's code is broken. Check first — if it's , the gate is doing its job. Make the val public with , grant the caller's org, or use a bypass token.
httpPrivacyrestrictedset_http_privacy未通过身份验证的请求不会到达val。平台会返回重定向到Val Town的登录或授权页面。这是调试受限val时最常见的困惑来源:
302- 会报告存在重定向,但不会自动跟随。
fetch_val_endpoint - 会显示重定向到
curl的val.town响应,而非你预期的返回结果。302 - API客户端会收到登录页面的HTML,而不是预期的JSON。
- 已登录但没有访问权限的访问者会收到响应,说明他们需要获得所在组织的访问授权。
403
这些情况都不代表val的代码有问题。请先检查——如果是,说明网关正在正常工作。你可以通过将val设为公开,或者授予调用方所在组织访问权,或是使用绕过令牌。
httpPrivacyrestrictedset_http_privacyAutomation and webhooks
自动化与Webhook
Machines can't complete a login redirect, so a restricted val that receives webhooks (Stripe, GitHub, a cron job in another val) needs a bypass token — a secret scoped to that one val.
Create it with ; the secret is shown once and cannot be retrieved again. Manage tokens with and .
create_bypass_tokenlist_bypass_tokensrevoke_bypass_tokenPresent it either way:
ts
// Header (preferred — keeps the secret out of logs and referrers)
await fetch(url, { headers: { "X-Val-Town-Access": Deno.env.get("MY_BYPASS_TOKEN")! } });
// Query param (for services that only accept a URL, e.g. some webhook configs)
await fetch(`${url}?val_town_access=${Deno.env.get("MY_BYPASS_TOKEN")}`);The platform strips the header and the query param before your handler runs, so your code never sees them. A bypass-token request carries no viewer identity — it is an anonymous machine caller.
机器无法完成登录重定向流程,因此接收webhook(Stripe、GitHub、另一个val中的定时任务等)的受限val需要使用绕过令牌——一种仅对当前val有效的密钥。
通过创建绕过令牌;密钥只会显示一次,之后无法再次获取。可以使用和管理令牌。
create_bypass_tokenlist_bypass_tokensrevoke_bypass_token可以通过以下任意一种方式传递令牌:
ts
// Header (preferred — keeps the secret out of logs and referrers)
await fetch(url, { headers: { "X-Val-Town-Access": Deno.env.get("MY_BYPASS_TOKEN")! } });
// Query param (for services that only accept a URL, e.g. some webhook configs)
await fetch(`${url}?val_town_access=${Deno.env.get("MY_BYPASS_TOKEN")}`);平台会在你的处理函数运行前移除该标头和查询参数,因此你的代码永远不会看到它们。使用绕过令牌的请求不携带访问者身份信息——属于匿名的机器调用。
Identifying the viewer
识别访问者身份
For a human viewer who came in through the gate, the platform forwards a short-lived signed header. It is not the identity itself — exchange it for the viewer's profile using the val's own API token, which Val Town injects as the environment variable:
X-Val-Town-Uservaltownts
const IDENTITY_HEADER = "X-Val-Town-User";
/** Returns the viewer's public profile, or null when there isn't one. */
async function getViewer(req: Request) {
const signed = req.headers.get(IDENTITY_HEADER);
if (!signed) return null;
const res = await fetch("https://api.val.town/v3/val/viewer", {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
[IDENTITY_HEADER]: signed,
},
});
if (!res.ok) return null;
// { id, username, type, bio, profileImageUrl, url, links }
return await res.json();
}Rules that matter:
- Always treat the viewer as optional. There is none on a public val or a bypass-token request. Never -assert it or index into a null result.
! - Resolve it server-side, on each request. The credential is short-lived and tied to your val — don't persist it, hand it to the browser, or attempt the lookup from client-side code.
- Only public profile fields come back — handle, bio, avatar, profile URL. No email address, no billing tier. Don't build a val that depends on those.
The transport above ( plus the exchange) is how this works today and may change; the three rules hold regardless.
X-Val-Town-User/v3/val/viewer对于通过网关校验的人类访问者,平台会转发一个短期有效的签名标头。这个标头本身不是身份信息——你需要使用val自身的API令牌(Val Town会将其作为环境变量注入)来交换获取访问者的个人资料:
X-Val-Town-Uservaltownts
const IDENTITY_HEADER = "X-Val-Town-User";
/** Returns the viewer's public profile, or null when there isn't one. */
async function getViewer(req: Request) {
const signed = req.headers.get(IDENTITY_HEADER);
if (!signed) return null;
const res = await fetch("https://api.val.town/v3/val/viewer", {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
[IDENTITY_HEADER]: signed,
},
});
if (!res.ok) return null;
// { id, username, type, bio, profileImageUrl, url, links }
return await res.json();
}重要规则:
- 始终将访问者视为可选的。 公开val或使用绕过令牌的请求不会有访问者身份。永远不要使用断言它,也不要对null结果进行索引访问。
! - 在服务端的每次请求中解析身份。 凭证是短期有效的,且与你的val绑定——不要持久化它,不要将它传递给浏览器,也不要尝试在客户端代码中进行查询。
- 仅会返回公开的个人资料字段 —— 用户名、简介、头像、个人主页URL。不包含邮箱地址,也不包含付费套餐等级。不要构建依赖这些信息的val。
上述传输方式(加上接口交换)是当前的实现方式,未来可能会变化;但以上三条规则始终适用。
X-Val-Town-User/v3/val/viewerManaging app access
应用访问管理
| Task | Tool |
|---|---|
| Check the current setting | |
| Make an endpoint public or restricted | |
| See who has access | |
| Grant / revoke an org | |
| Create / list / revoke automation secrets | |
Restricted vals can only be iframed by val.town, so an embed of one on an external site will be blocked by the browser regardless of who's logged in.
| 任务 | 工具 |
|---|---|
| 查看当前设置 | |
| 将端点设为公开或受限 | |
| 查看拥有访问权的对象 | |
| 授予/撤销组织访问权 | |
| 创建/查看/撤销自动化密钥 | |
受限val只能被val.town嵌入iframe,因此无论用户是否登录,在外部网站嵌入受限val都会被浏览器拦截。