@webtwizz/cloud 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_GUIDE.md +22 -2
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/dist/server.js +4 -3
- package/package.json +4 -2
package/AI_GUIDE.md
CHANGED
|
@@ -41,7 +41,7 @@ if (error) setMessage(authMessage(error));
|
|
|
41
41
|
|
|
42
42
|
For wording that fits the site, pass overrides: `authMessage(error, { USER_ALREADY_EXISTS: 'You already have a booking account.' })`.
|
|
43
43
|
|
|
44
|
-
When the owner turns on Confirm email, sign-up returns no session and the 6-digit code is emailed automatically. Show a code step: `authClient.emailOtp.verifyEmail({ email, otp })`, then sign them in, with a "Send a new code" button that calls `authClient.emailOtp.sendVerificationOtp({ email, type: 'email-verification' })`. Never send the first code yourself.
|
|
44
|
+
Confirm email is off unless the owner turns it on in Settings > Cloud > Users, so sign-up signs people straight in and an unconfirmed email never stops a sign-in. When the owner turns on Confirm email, sign-up returns no session and the 6-digit code is emailed automatically. Show a code step: `authClient.emailOtp.verifyEmail({ email, otp })`, then sign them in, with a "Send a new code" button that calls `authClient.emailOtp.sendVerificationOtp({ email, type: 'email-verification' })`. Never send the first code yourself.
|
|
45
45
|
|
|
46
46
|
5. Protect pages in `src/proxy.ts`, listing only the protected routes in the matcher (a matcher over every path blocks the sign-in page's own scripts):
|
|
47
47
|
|
|
@@ -66,6 +66,15 @@ Never destructure `{ user }` from the top level.
|
|
|
66
66
|
|
|
67
67
|
8. Every sign-in page gets a "Forgot password?" link to `/reset-password`, a client page in two steps: their email, then `authClient.forgetPassword.emailOtp({ email })`, which emails a 6-digit code; then the code and a new password, then `authClient.emailOtp.resetPassword({ email, otp, password })`, then send them to sign in. Leave `/reset-password` out of the proxy matcher. The owner can also send a reset code from the Data panel, and it lands on this page.
|
|
68
68
|
|
|
69
|
+
9. Admins are accounts whose role is `'admin'`. Admin pages and admin writes check it on the server, the same way as step 6:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const { data: session } = await auth.getSession();
|
|
73
|
+
if (session?.user?.role !== 'admin') return Response.json({ error: 'Admins only' }, { status: 403 });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The first admin is the owner's own account on the site: they sign up like any visitor (their WebTwizz account is separate), then make that account an admin under Users in the Data panel, or ask you to do it. A new role reaches their session within a few minutes, or right away when they sign in again.
|
|
77
|
+
|
|
69
78
|
Sign-ins are email and password only. Sign-in emails (reset codes, confirmations) are sent for the site automatically; never build email sending or add an email service for them. If the user asks for another way to sign in, say WebTwizz Cloud sign-ins are email and password for now and build that.
|
|
70
79
|
|
|
71
80
|
Sites built before this package import sign-ins from another package in `src/lib/auth/`. When you work on their sign-ins, move `server.ts`, `client.ts` and the auth route to the files above (same sign-ins, same users) and remove the old package.
|
|
@@ -102,6 +111,17 @@ The database can pause or fill up on the owner's plan. Wrap reads and saves in t
|
|
|
102
111
|
|
|
103
112
|
Pages that only show data set `export const revalidate = 60`, and the server action that saves calls `revalidatePath` for them. Pages for a signed-in user stay dynamic.
|
|
104
113
|
|
|
114
|
+
The database runs while pages read or save data and stops after 5 quiet minutes; its running time comes out of the owner's monthly allowance. So a page that refreshes data on a timer (a feed, a chat, a dashboard) refreshes only while the tab is visible, and again when the visitor comes back:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const refresh = () => { if (document.visibilityState === 'visible') load(); };
|
|
119
|
+
const timer = setInterval(refresh, 60_000);
|
|
120
|
+
document.addEventListener('visibilitychange', refresh);
|
|
121
|
+
return () => { clearInterval(timer); document.removeEventListener('visibilitychange', refresh); };
|
|
122
|
+
}, []);
|
|
123
|
+
```
|
|
124
|
+
|
|
105
125
|
## Files
|
|
106
126
|
|
|
107
127
|
Two buckets: `public` (anyone can view, for images shown on the site) and `private` (documents, opened through short-lived links). Images you place on pages yourself keep using the usual image tools; this is for what visitors or the site's features upload.
|
|
@@ -132,4 +152,4 @@ Show files with `createFiles().publicUrl(key)` in a plain `<img>` or link, or `a
|
|
|
132
152
|
|
|
133
153
|
## Owner tools
|
|
134
154
|
|
|
135
|
-
The owner manages users under Users in the Data panel (password reset, block, delete with their rows, export), sets who can sign up and the sign-in emails in Settings > Cloud > Users, and downloads everything from Settings > Cloud > Database. Point them there when they ask.
|
|
155
|
+
The owner manages users under Users in the Data panel (make admin, password reset, block, delete with their rows, export), sets who can sign up and the sign-in emails in Settings > Cloud > Users, and downloads everything from Settings > Cloud > Database. Point them there when they ask.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @webtwizz/cloud
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 67343d5: Signed-in visitors can sign out, and `authClient` can read their session: since 0.2.0 the sign-in route answered both with a server error inside Next, because it copied a request Next hands over as a Proxy. Visitors who pressed Sign out stayed signed in.
|
|
8
|
+
|
|
3
9
|
## 0.4.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -40,6 +40,8 @@ npm run build
|
|
|
40
40
|
|
|
41
41
|
`test/fixture` is a small Next app that CI builds against the packed tarball.
|
|
42
42
|
|
|
43
|
+
To try a change in the local WebTwizz preview site without publishing: `npm run preview` builds the package, puts it in the preview container's `node_modules` under a unique dev version, and restarts its dev server (about 10 seconds). `npm run dev` does the same on every save.
|
|
44
|
+
|
|
43
45
|
## Releases
|
|
44
46
|
|
|
45
47
|
- Add a changeset (`npx changeset`) to any PR that changes what sites get.
|
package/dist/server.js
CHANGED
|
@@ -1435,7 +1435,8 @@ function forLibrary(request, make) {
|
|
|
1435
1435
|
if (translated === cookie) return request;
|
|
1436
1436
|
const headers2 = new Headers(request.headers);
|
|
1437
1437
|
headers2.set("cookie", translated);
|
|
1438
|
-
|
|
1438
|
+
const body = request.method === "GET" || request.method === "HEAD" ? void 0 : request.body;
|
|
1439
|
+
return make(request.url, { method: request.method, headers: headers2, body, signal: request.signal, ...body ? { duplex: "half" } : {} });
|
|
1439
1440
|
}
|
|
1440
1441
|
function forBrowser(response) {
|
|
1441
1442
|
const provider = [...response.headers.keys()].filter((name) => PROVIDER_HEADER.test(name));
|
|
@@ -1481,7 +1482,7 @@ function createAuth(options = {}) {
|
|
|
1481
1482
|
const middleware = auth.middleware.bind(auth);
|
|
1482
1483
|
auth.handler = () => {
|
|
1483
1484
|
const methods = handler();
|
|
1484
|
-
const wrap = (method) => async (request, context) => forBrowser(await method(forLibrary(request, (
|
|
1485
|
+
const wrap = (method) => async (request, context) => forBrowser(await method(forLibrary(request, (url, init) => new Request(url, init)), context));
|
|
1485
1486
|
const post = wrap(methods.POST);
|
|
1486
1487
|
const signUp = async (request, context) => {
|
|
1487
1488
|
const response = await post(request, context);
|
|
@@ -1493,7 +1494,7 @@ function createAuth(options = {}) {
|
|
|
1493
1494
|
};
|
|
1494
1495
|
auth.middleware = (config) => {
|
|
1495
1496
|
const run = middleware(config);
|
|
1496
|
-
return async (request) => forBrowser(await run(forLibrary(request, (
|
|
1497
|
+
return async (request) => forBrowser(await run(forLibrary(request, (url, init) => new NextRequest2(url, init))));
|
|
1497
1498
|
};
|
|
1498
1499
|
return auth;
|
|
1499
1500
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtwizz/cloud",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Sign-ins, database and file storage for sites built on WebTwizz Cloud",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -55,7 +55,9 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
56
56
|
"test": "vitest run",
|
|
57
57
|
"test:integration": "npm run build && vitest run --config vitest.integration.config.ts",
|
|
58
|
-
"prepublishOnly": "npm run build && npm run typecheck && npm test"
|
|
58
|
+
"prepublishOnly": "npm run build && npm run typecheck && npm test",
|
|
59
|
+
"preview": "tsup && node scripts/preview.mjs",
|
|
60
|
+
"dev": "tsup --watch --onSuccess \"node scripts/preview.mjs\""
|
|
59
61
|
},
|
|
60
62
|
"engines": {
|
|
61
63
|
"node": ">=20"
|