You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c6330e8
Browse filesBrowse the repository at this point in the historyBrowse files
- Document visitor connector roles, the Connect button, and
visitor_role_not_bindable.
- Guard the Worker write examples by method and a bearer secret.
- Quote shell placeholders as variables so copied commands run.
- Encode the redeem URL with URLSearchParams.
- Add steps to replace an identity connection without losing access.
- State that removed members cannot rejoin a subteam with Join.
- List every root Worker entry name.
- State that a new connector run answers 201 even when it fails.
- Separate unknown version IDs (exit 20) from unmatched refs (exit 2).
- Pass --space explicitly after sf migrate, and state the claim deadline.
- Match the sf feedback output sample to the CLI.
@@ -114,7 +114,7 @@ A `signer` connection sends visitors to your portal, and your portal vouches for
114
114
1. The visitor presses **Continue with** on the access page. Spacefast redirects the browser to your `authorizeUrl` with the query parameters below.
115
115
2. Your portal signs the visitor in and signs a proof token.
116
116
3. Your server sends the proof to Spacefast and receives a short-lived token.
117
-
4. Your portal sends the browser to `https://<host>/__sf/redeem?sf_token=<token>&return=<return>`. The Space sets a session cookie and opens the page.
117
+
4. Your portal sends the browser to `/__sf/redeem` on `host`, with the `sf_token` and `return` query parameters. The Space sets a session cookie and opens the page.
118
118
119
119
| Query parameter | What it holds |
120
120
| --- | --- |
@@ -143,14 +143,20 @@ Send the proof from your server, not from the browser:
A `200` answers with `token` and `expiresAt`. A `403` means the proof failed or no grant admits that subject to `resource`.
152
152
153
-
Redirect the browser to `/__sf/redeem` on the same `host` within 60 seconds. The token works once, and only in the browser that started the sign-in.
153
+
Redirect the browser to `/__sf/redeem` on the same `host` within 60 seconds. The token works once, and only in the browser that started the sign-in. Encode each value as one query parameter. A `return` such as `/docs?tab=a&sort=b` holds `&` and `=`, so do not join the strings by hand:
@@ -163,7 +169,7 @@ In **SSO providers**, press **Update** on the connection's row.
163
169
164
170
Leave **Replacement client secret** blank to keep the current secret. To rename a connection or change a signer's sign-in URL, use the API or the CLI.
165
171
166
-
[`updateTeamIdentityConnection`](/api/reference/teams/updateteamidentityconnection) accepts `name`, `clientId`, `clientSecret`, `authorizeUrl`, `publicKeys`, and `sessionTtlSeconds`. It accepts `sessionTtlSeconds` from 60 to 86,400. For a signer, Spacefast lowers any value above 1,800 to 1,800. `publicKeys` replaces the whole key set. The `type` and `issuer` cannot change. To change them, create a new connection.
172
+
[`updateTeamIdentityConnection`](/api/reference/teams/updateteamidentityconnection) accepts `name`, `clientId`, `clientSecret`, `authorizeUrl`, `publicKeys`, and `sessionTtlSeconds`. It accepts `sessionTtlSeconds` from 60 to 86,400. For a signer, Spacefast lowers any value above 1,800 to 1,800. `publicKeys` replaces the whole key set. The `type` and `issuer` cannot change.
167
173
168
174
Grants that use the connection keep working. A change to the client ID, client secret, keys, or session lifetime sets `rotatedAt`.
169
175
@@ -173,13 +179,20 @@ To rotate a signer key without a sign-in gap:
173
179
2. Sign new proofs with the new key.
174
180
3. Send a key set that holds only the new key.
175
181
182
+
### Replace a connection
183
+
184
+
A team can have one active connection per issuer. Revoking a connection also revokes its grants. Plan the order:
185
+
186
+
-**To move to a new issuer**, create the new connection and its grants first. Then revoke the old connection.
187
+
-**To change the type for the same issuer**, revoke the old connection first, because the new one uses the same issuer. Then create the new connection and add its grants again. Visitors cannot sign in through that issuer until the new grants exist.
188
+
176
189
## Revoke a connection
177
190
178
191
Press **Revoke** on the row, then **Revoke connection** in the dialog. Over HTTP, send [`revokeTeamIdentityConnection`](/api/reference/teams/revoketeamidentityconnection). It answers `204`. From a terminal, run `sf share identity revoke`.
179
192
180
193
Revoking removes the grants too. Spacefast revokes every grant on the team's Spaces that trusts the connection's issuer, then the connection itself.
181
194
182
-
A revoked connection stays in [`listTeamIdentityConnections`](/api/reference/teams/listteamidentityconnections) with `revokedAt` set. It cannot be updated. To restore access, create a new connection and new grants.
195
+
A revoked connection stays in [`listTeamIdentityConnections`](/api/reference/teams/listteamidentityconnections) with `revokedAt` set. It cannot be updated. To restore access, create a new connection and add its grants again.
Copy file name to clipboardExpand all lines: content/(account)/subteams.mdx
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ Subteams need an Enterprise parent team. On every other plan, subteam operations
12
12
## How subteams work
13
13
14
14
-**The parent's owners and admins are in every subteam.** They hold the same role there. To change that role, change it on the parent team.
15
-
-**Parent members can join a subteam.**Every subteam appears in **Teams you can join** for parent members. Pressing **Join** makes them a subteam `member`, with no new seat.
15
+
-**Parent members can join a subteam.**A subteam appears in **Teams you can join** for parent members. Pressing **Join** makes them a subteam `member`, with no new seat. A person removed from a subteam does not see it there. An owner or admin of the parent team must add them again.
16
16
-**Seats are shared.** Seats count across the parent and all its subteams. A person in several teams of the family uses one seat.
17
17
-**Subteams do not nest.** A subteam cannot have subteams of its own.
18
18
-**Leaving the parent leaves every subteam.** When someone leaves or is removed from the parent team, they also lose every subteam membership.
@@ -32,8 +32,8 @@ Spacefast creates the subteam and opens it. Its URL slug starts with the parent'
32
32
Over HTTP, send [`createSubTeam`](/api/reference/teams/createsubteam) with a `name` of 1 to 255 characters:
Copy file name to clipboardExpand all lines: content/(dynamic)/functions.mdx
+9-5Lines changed: 9 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -148,7 +148,7 @@ Two more bindings appear on `env` when the version needs them. Run `sf functions
148
148
|`env.CONNECTORS`| The version's capsule declares connector roles, or the Space has a connector binding | A connector role |
149
149
|`env.ZERO`| The version includes a [Zero](/zero-runtime) capsule | The capsule's queries and mutations |
150
150
151
-
A Zero project ships a Worker when it also has a `functions/` directory or a root `handler.ts`. `sf publish` compiles the capsule and the Worker into one version.
151
+
A Zero project ships a Worker when it also has a `functions/` directory or a root `handler.ts`, `handler.js`, or `handler.mjs`. `sf publish` compiles the capsule and the Worker into one version.
152
152
153
153
### `env.CONNECTORS`
154
154
@@ -159,16 +159,20 @@ A Zero project ships a Worker when it also has a `functions/` directory or a roo
159
159
```js handler.js
160
160
exportdefault {
161
161
asyncfetch(request, env) {
162
-
constnotes=awaitenv.ZERO.query("notes");
163
-
awaitenv.ZERO.mutate("addNote", "Written by the Worker");
164
-
returnResponse.json(notes);
162
+
if (request.method!=="POST") returnResponse.json(awaitenv.ZERO.query("notes"));
163
+
constsecret=env.NOTES_SECRET;
164
+
if (!secret ||request.headers.get("authorization") !==`Bearer ${secret}`) {
`env.ZERO.query(name, ...args)` resolves to the query's data. `env.ZERO.mutate(name, ...args)` resolves to the mutation's return value. There is no call for actions. A failed call throws an `Error` with a `code`. The code is `zero_upstream_unavailable` when the capsule cannot be reached.
170
174
171
-
Both bindings forward the request's cookie. The runtime verifies the signed Zero identity cookie before it acts for that visitor. When no visitor is signed in, `env.ZERO` runs as the Space's service principal. In the capsule handler, `ctx.auth.userId` is then `service:<spaceId>` and `ctx.auth.provider` is `service`. `ctx.auth.isAuthenticated` is `true`, so a sign-in check alone does not keep the Worker out.
175
+
Both bindings forward the request's cookie. The runtime verifies the signed Zero identity cookie before it acts for that visitor. When no visitor is signed in, `env.ZERO` runs as the Space's service principal. In the capsule handler, `ctx.auth.userId` is then `service:<spaceId>` and `ctx.auth.provider` is `service`. `ctx.auth.isAuthenticated` is `true`, so a sign-in check alone does not keep the Worker out. Check the caller in the Worker before it calls a mutation. The example requires a secret that you set with `sf env set NOTES_SECRET --value-from-stdin`.
Copy file name to clipboardExpand all lines: content/(dynamic)/zero-connectors.mdx
+37-7Lines changed: 37 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -35,13 +35,14 @@ export default capsule({
35
35
36
36
| Key | Required | What it does |
37
37
| --- | --- | --- |
38
-
|`as`| yes | Whose account the role uses: `team`or `owner`|
39
-
|`connector`| no | The connector slug for the role. The dashboard offers only connections for this connector |
38
+
|`as`| yes | Whose account the role uses: `team`, `owner`, or `visitor`|
39
+
|`connector`| no | The connector slug for the role. The dashboard offers only connections for this connector. A `visitor` role needs it|
40
40
41
41
|`as`| Account | Condition |
42
42
| --- | --- | --- |
43
43
|`team`| A team connection | The connection allows sites to use it |
44
44
|`owner`| Your own personal connection | The Space is private |
45
+
|`visitor`| Each signed-in visitor's own account | The visitor connects it on the Space. See [Let visitors connect their own account](#let-visitors-connect-their-own-account)|
45
46
46
47
A role name starts with a lowercase letter and holds only letters and digits, up to 64 characters. The compiler reads the `connectors` map from source without running it, so write it as a literal. A map that is not a literal object of roles fails the publish with "capsule() connectors must be a role map."
47
48
@@ -55,6 +56,7 @@ The connection must fit the role:
55
56
56
57
- A `team` role needs a team connection that allows sites to use it. Turn that on with **Allow sites to use this account** in the dashboard, `sf connectors connections edit <connection> --serving`, or [`updateConnection`](/api/reference/connectors/updateconnection) with `serving: true`.
57
58
- An `owner` role needs a personal connection that you created, on a private Space. If the Space stops being private, Spacefast removes its owner bindings.
59
+
- A `visitor` role cannot be bound. Binding one fails with `visitor_role_not_bindable`.
58
60
59
61
The request must act for a user who can manage the Space. A credential that acts for no user gets `forbidden`.
A `visitor` role calls the service as the signed-in visitor, with that visitor's own account. Nobody on the team binds it, and the dashboard row has no **Bind account** button.
1. A call returns `connect_required` until the visitor connects an account.
104
+
2.`ConnectorGate` shows a **Connect** button for that state. The button opens the service's sign-in in a popup.
105
+
3. A visitor who is not signed in to the Space signs in first.
106
+
4. After the service sign-in, the popup closes and the gate runs the call again.
107
+
108
+
The connector must support OAuth sign-in. Each connection belongs to one visitor, one Space, and one role. For `reconnect_required`, the gate shows **Reconnect** instead.
109
+
110
+
To disconnect, call `disconnect()` on the result of `useConnectorState(result, role)`. To see who connected, call [`listVisitorConnectionUsers`](/api/reference/spaces/listvisitorconnectionusers). It returns counts and roles per Space user, not the accounts. To disconnect one user, call [`revokeVisitorConnection`](/api/reference/spaces/revokevisitorconnection).
111
+
91
112
## Call a role from Zero
92
113
93
114
`ctx.connectors.<role>` is on every query, mutation, action, and endpoint context. The property path after the role is the tool name, joined with dots. `ctx.connectors.tracker.issues.create(args)` calls the `issues.create` tool on the bound connection.
@@ -135,19 +156,27 @@ To read the state of one result yourself, call `useConnectorState(result)` from
135
156
136
157
## Call a role from a Worker
137
158
138
-
A [Worker](/functions) gets `env.CONNECTORS` when the version's capsule declares roles or the Space has a binding. To ship a Worker beside a capsule, add a `functions/` directory or a root `handler.ts` to the Zero project. `sf publish` compiles both into one version.
159
+
A [Worker](/functions) gets `env.CONNECTORS` when the version's capsule declares roles or the Space has a binding. To ship a Worker beside a capsule, add a `functions/` directory or a root `handler.ts`, `handler.js`, or `handler.mjs` to the Zero project. `sf publish` compiles both into one version.
139
160
140
161
The role proxy works as it does in Zero. It resolves to the tool's result and throws a `ConnectorCallError` for any other state.
141
162
142
163
```js handler.js
143
164
exportdefault {
144
165
asyncfetch(request, env) {
145
-
constissue=awaitenv.CONNECTORS.tracker.issues.create({ title:"Checkout is down" });
146
-
returnResponse.json(issue);
166
+
if (request.method!=="POST") returnnewResponse("Method not allowed", { status:405 });
167
+
constsecret=env.REPORT_SECRET;
168
+
if (!secret ||request.headers.get("authorization") !==`Bearer ${secret}`) {
constissue=awaitenv.CONNECTORS.tracker.issues.create({ title });
173
+
returnResponse.json(issue, { status:201 });
147
174
},
148
175
};
149
176
```
150
177
178
+
A `team` role acts with the team's account for every request that reaches the Worker. Call a tool that writes only on a write method, and only after you check the caller. This example requires a secret that you set with `sf env set REPORT_SECRET --value-from-stdin`.
179
+
151
180
To handle every state without a throw, call `env.CONNECTORS.call(role, tool, args)`. It resolves to `{ state, run }`. Check `state.code` before you read `run`.
152
181
153
182
```js handler.js
@@ -173,8 +202,8 @@ Every call ends in one state. `state.message` carries the text below.
173
202
|`ok`| Connected. | The tool ran |
174
203
|`unbound`| The site owner needs to connect this service. | No usable connection is bound to the role, or the Space has no team. An owner binding stops working when its owner leaves the team |
175
204
|`forbidden`| You do not have permission to use this connection. | The bound connection does not fit the role, policy blocks the tool, or a Zero read handler called a tool that writes |
176
-
|`connect_required`| Connect your account to continue. | The bound connection is still waiting for authorization |
177
-
|`reconnect_required`| Reconnect your account to continue. | The bound connection was revoked or needs a new sign-in |
205
+
|`connect_required`| Connect your account to continue. | The bound connection is still waiting for authorization, or the visitor has not connected an account for a `visitor` role|
206
+
|`reconnect_required`| Reconnect your account to continue. | The connection was revoked or needs a new sign-in |
178
207
|`approval_pending`| This action is waiting for approval. | Policy parked the call. `pendingReason` is `approval`, and `approvalUrl` links to the decision |
179
208
|`approval_pending`| Still working on it. Try again in a moment. | The run did not finish within 25 seconds. `pendingReason` is `running`|
180
209
|`unavailable`| This service is unavailable. Try again. | The tool failed for another reason |
@@ -203,6 +232,7 @@ Each call has an idempotency key built from the version, the request id, the han
203
232
| --- | --- |
204
233
|`space_must_be_claimed`| The Space does not belong to a team |
205
234
|`connector_role_unknown`| The live version does not declare the role |
235
+
|`visitor_role_not_bindable`| The role is a `visitor` role. Each visitor connects their own account |
206
236
|`connection_serving_not_granted`| A `team` role got a personal connection, or a team connection that does not allow sites to use it |
207
237
|`owner_binding_requires_private_space`| An `owner` role got a connection that is not your personal one, or the Space is not private |
208
238
|`forbidden`| The credential does not act for a user |
Copy file name to clipboardExpand all lines: content/(publish)/migrate.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -110,9 +110,9 @@ To return at once and check later, pass `--no-wait`. The receipt has the build I
110
110
111
111
## Finish the move
112
112
113
-
1. To keep an anonymous copy, open the **Claim** link and pick a team. See [Publish without an account](/anonymous-and-claim).
113
+
1. To keep an anonymous copy, open the **Claim** link and pick a team. An unclaimed copy stops serving after 33 hours and 20 minutes. See [The clock](/anonymous-and-claim#the-clock).
114
114
2. To serve the copy on your own domain, attach the domain to the new Space. See [Custom domains](/domains).
115
-
3. To edit the copy after you claim it, download it with `sf spaces download`. Change the files, then publish them with `sf publish`.
115
+
3. To edit the copy after you claim it, download it with `sf spaces download --space` and the ID from the `Space` line. Change the files, then publish them with `sf publish --space` and the same ID.
0 commit comments