@wizeworks/lens-cloud 0.0.0-stage → 0.3.0-alpha.2
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/LICENSE +21 -0
- package/README.md +115 -2
- package/dist/alerts/evaluate.d.ts +10 -0
- package/dist/alerts/evaluate.d.ts.map +1 -0
- package/dist/alerts/evaluate.js +81 -0
- package/dist/alerts/evaluate.js.map +1 -0
- package/dist/alerts/notify.d.ts +28 -0
- package/dist/alerts/notify.d.ts.map +1 -0
- package/dist/alerts/notify.js +52 -0
- package/dist/alerts/notify.js.map +1 -0
- package/dist/app.d.ts +26 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/auth.d.ts +51 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +63 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/bridge.d.ts +7 -0
- package/dist/auth/bridge.d.ts.map +1 -0
- package/dist/auth/bridge.js +39 -0
- package/dist/auth/bridge.js.map +1 -0
- package/dist/auth/crypto.d.ts +14 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +16 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/delete-user.d.ts +16 -0
- package/dist/auth/delete-user.d.ts.map +1 -0
- package/dist/auth/delete-user.js +21 -0
- package/dist/auth/delete-user.js.map +1 -0
- package/dist/auth/deps.d.ts +27 -0
- package/dist/auth/deps.d.ts.map +1 -0
- package/dist/auth/deps.js +3 -0
- package/dist/auth/deps.js.map +1 -0
- package/dist/auth/email-token.d.ts +8 -0
- package/dist/auth/email-token.d.ts.map +1 -0
- package/dist/auth/email-token.js +15 -0
- package/dist/auth/email-token.js.map +1 -0
- package/dist/auth/hooks.d.ts +4 -0
- package/dist/auth/hooks.d.ts.map +1 -0
- package/dist/auth/hooks.js +143 -0
- package/dist/auth/hooks.js.map +1 -0
- package/dist/auth/options.d.ts +17 -0
- package/dist/auth/options.d.ts.map +1 -0
- package/dist/auth/options.js +98 -0
- package/dist/auth/options.js.map +1 -0
- package/dist/auth/password.d.ts +10 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +22 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/plugins.d.ts +4 -0
- package/dist/auth/plugins.d.ts.map +1 -0
- package/dist/auth/plugins.js +45 -0
- package/dist/auth/plugins.js.map +1 -0
- package/dist/auth/principal.d.ts +38 -0
- package/dist/auth/principal.d.ts.map +1 -0
- package/dist/auth/principal.js +69 -0
- package/dist/auth/principal.js.map +1 -0
- package/dist/auth/roles.d.ts +50 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +18 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/schema-names.d.ts +130 -0
- package/dist/auth/schema-names.d.ts.map +1 -0
- package/dist/auth/schema-names.js +53 -0
- package/dist/auth/schema-names.js.map +1 -0
- package/dist/auth/secret.d.ts +12 -0
- package/dist/auth/secret.d.ts.map +1 -0
- package/dist/auth/secret.js +32 -0
- package/dist/auth/secret.js.map +1 -0
- package/dist/auth/security-notices.d.ts +34 -0
- package/dist/auth/security-notices.d.ts.map +1 -0
- package/dist/auth/security-notices.js +81 -0
- package/dist/auth/security-notices.js.map +1 -0
- package/dist/auth/signup.d.ts +23 -0
- package/dist/auth/signup.d.ts.map +1 -0
- package/dist/auth/signup.js +61 -0
- package/dist/auth/signup.js.map +1 -0
- package/dist/auth/sso.d.ts +21 -0
- package/dist/auth/sso.d.ts.map +1 -0
- package/dist/auth/sso.js +35 -0
- package/dist/auth/sso.js.map +1 -0
- package/dist/auth/wire.d.ts +16 -0
- package/dist/auth/wire.d.ts.map +1 -0
- package/dist/auth/wire.js +47 -0
- package/dist/auth/wire.js.map +1 -0
- package/dist/cli-args.d.ts +5 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +86 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +55 -0
- package/dist/config.js.map +1 -0
- package/dist/db/db.d.ts +22 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +64 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/migrate.d.ts +6 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +35 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema-auth.d.ts +2 -0
- package/dist/db/schema-auth.d.ts.map +1 -0
- package/dist/db/schema-auth.js +93 -0
- package/dist/db/schema-auth.js.map +1 -0
- package/dist/db/schema-mail.d.ts +2 -0
- package/dist/db/schema-mail.d.ts.map +1 -0
- package/dist/db/schema-mail.js +8 -0
- package/dist/db/schema-mail.js.map +1 -0
- package/dist/db/schema-notify.d.ts +2 -0
- package/dist/db/schema-notify.d.ts.map +1 -0
- package/dist/db/schema-notify.js +26 -0
- package/dist/db/schema-notify.js.map +1 -0
- package/dist/db/schema-sso.d.ts +2 -0
- package/dist/db/schema-sso.d.ts.map +1 -0
- package/dist/db/schema-sso.js +17 -0
- package/dist/db/schema-sso.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +208 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/http/http.d.ts +19 -0
- package/dist/http/http.d.ts.map +1 -0
- package/dist/http/http.js +66 -0
- package/dist/http/http.js.map +1 -0
- package/dist/http/limiter.d.ts +11 -0
- package/dist/http/limiter.d.ts.map +1 -0
- package/dist/http/limiter.js +31 -0
- package/dist/http/limiter.js.map +1 -0
- package/dist/http/router.d.ts +21 -0
- package/dist/http/router.d.ts.map +1 -0
- package/dist/http/router.js +28 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +4 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +53 -0
- package/dist/jobs.js.map +1 -0
- package/dist/mail/blocks.d.ts +14 -0
- package/dist/mail/blocks.d.ts.map +1 -0
- package/dist/mail/blocks.js +35 -0
- package/dist/mail/blocks.js.map +1 -0
- package/dist/mail/content.d.ts +68 -0
- package/dist/mail/content.d.ts.map +1 -0
- package/dist/mail/content.js +6 -0
- package/dist/mail/content.js.map +1 -0
- package/dist/mail/emails-digest.d.ts +10 -0
- package/dist/mail/emails-digest.d.ts.map +1 -0
- package/dist/mail/emails-digest.js +55 -0
- package/dist/mail/emails-digest.js.map +1 -0
- package/dist/mail/emails-health.d.ts +6 -0
- package/dist/mail/emails-health.d.ts.map +1 -0
- package/dist/mail/emails-health.js +36 -0
- package/dist/mail/emails-health.js.map +1 -0
- package/dist/mail/emails-security.d.ts +14 -0
- package/dist/mail/emails-security.d.ts.map +1 -0
- package/dist/mail/emails-security.js +27 -0
- package/dist/mail/emails-security.js.map +1 -0
- package/dist/mail/emails-team.d.ts +11 -0
- package/dist/mail/emails-team.d.ts.map +1 -0
- package/dist/mail/emails-team.js +45 -0
- package/dist/mail/emails-team.js.map +1 -0
- package/dist/mail/emails.d.ts +9 -0
- package/dist/mail/emails.d.ts.map +1 -0
- package/dist/mail/emails.js +49 -0
- package/dist/mail/emails.js.map +1 -0
- package/dist/mail/layout.d.ts +6 -0
- package/dist/mail/layout.d.ts.map +1 -0
- package/dist/mail/layout.js +10 -0
- package/dist/mail/layout.js.map +1 -0
- package/dist/mail/mailer.d.ts +25 -0
- package/dist/mail/mailer.d.ts.map +1 -0
- package/dist/mail/mailer.js +49 -0
- package/dist/mail/mailer.js.map +1 -0
- package/dist/mail/preview.d.ts +6 -0
- package/dist/mail/preview.d.ts.map +1 -0
- package/dist/mail/preview.js +26 -0
- package/dist/mail/preview.js.map +1 -0
- package/dist/mail/resend.d.ts +11 -0
- package/dist/mail/resend.d.ts.map +1 -0
- package/dist/mail/resend.js +34 -0
- package/dist/mail/resend.js.map +1 -0
- package/dist/mail/samples.d.ts +3 -0
- package/dist/mail/samples.d.ts.map +1 -0
- package/dist/mail/samples.js +63 -0
- package/dist/mail/samples.js.map +1 -0
- package/dist/mail/stop-route.d.ts +4 -0
- package/dist/mail/stop-route.d.ts.map +1 -0
- package/dist/mail/stop-route.js +41 -0
- package/dist/mail/stop-route.js.map +1 -0
- package/dist/mail/svix.d.ts +8 -0
- package/dist/mail/svix.d.ts.map +1 -0
- package/dist/mail/svix.js +17 -0
- package/dist/mail/svix.js.map +1 -0
- package/dist/mail/templates.d.ts +12 -0
- package/dist/mail/templates.d.ts.map +1 -0
- package/dist/mail/templates.js +18 -0
- package/dist/mail/templates.js.map +1 -0
- package/dist/mail/text.d.ts +4 -0
- package/dist/mail/text.d.ts.map +1 -0
- package/dist/mail/text.js +17 -0
- package/dist/mail/text.js.map +1 -0
- package/dist/mail/theme.d.ts +26 -0
- package/dist/mail/theme.d.ts.map +1 -0
- package/dist/mail/theme.js +21 -0
- package/dist/mail/theme.js.map +1 -0
- package/dist/mail/unsubscribe.d.ts +14 -0
- package/dist/mail/unsubscribe.d.ts.map +1 -0
- package/dist/mail/unsubscribe.js +39 -0
- package/dist/mail/unsubscribe.js.map +1 -0
- package/dist/mail/webhook.d.ts +5 -0
- package/dist/mail/webhook.d.ts.map +1 -0
- package/dist/mail/webhook.js +41 -0
- package/dist/mail/webhook.js.map +1 -0
- package/dist/notify/digest-data.d.ts +31 -0
- package/dist/notify/digest-data.d.ts.map +1 -0
- package/dist/notify/digest-data.js +37 -0
- package/dist/notify/digest-data.js.map +1 -0
- package/dist/notify/digest.d.ts +17 -0
- package/dist/notify/digest.d.ts.map +1 -0
- package/dist/notify/digest.js +76 -0
- package/dist/notify/digest.js.map +1 -0
- package/dist/notify/health.d.ts +6 -0
- package/dist/notify/health.d.ts.map +1 -0
- package/dist/notify/health.js +43 -0
- package/dist/notify/health.js.map +1 -0
- package/dist/notify/team-describe.d.ts +22 -0
- package/dist/notify/team-describe.d.ts.map +1 -0
- package/dist/notify/team-describe.js +67 -0
- package/dist/notify/team-describe.js.map +1 -0
- package/dist/notify/team.d.ts +6 -0
- package/dist/notify/team.d.ts.map +1 -0
- package/dist/notify/team.js +56 -0
- package/dist/notify/team.js.map +1 -0
- package/dist/repo/alerts.d.ts +52 -0
- package/dist/repo/alerts.d.ts.map +1 -0
- package/dist/repo/alerts.js +38 -0
- package/dist/repo/alerts.js.map +1 -0
- package/dist/repo/audit.d.ts +11 -0
- package/dist/repo/audit.d.ts.map +1 -0
- package/dist/repo/audit.js +17 -0
- package/dist/repo/audit.js.map +1 -0
- package/dist/repo/auth-prune.d.ts +4 -0
- package/dist/repo/auth-prune.d.ts.map +1 -0
- package/dist/repo/auth-prune.js +8 -0
- package/dist/repo/auth-prune.js.map +1 -0
- package/dist/repo/mail-prefs.d.ts +35 -0
- package/dist/repo/mail-prefs.d.ts.map +1 -0
- package/dist/repo/mail-prefs.js +34 -0
- package/dist/repo/mail-prefs.js.map +1 -0
- package/dist/repo/metric-read.d.ts +12 -0
- package/dist/repo/metric-read.d.ts.map +1 -0
- package/dist/repo/metric-read.js +11 -0
- package/dist/repo/metric-read.js.map +1 -0
- package/dist/repo/metric-write.d.ts +9 -0
- package/dist/repo/metric-write.d.ts.map +1 -0
- package/dist/repo/metric-write.js +53 -0
- package/dist/repo/metric-write.js.map +1 -0
- package/dist/repo/orgs.d.ts +37 -0
- package/dist/repo/orgs.d.ts.map +1 -0
- package/dist/repo/orgs.js +33 -0
- package/dist/repo/orgs.js.map +1 -0
- package/dist/repo/projects.d.ts +37 -0
- package/dist/repo/projects.d.ts.map +1 -0
- package/dist/repo/projects.js +57 -0
- package/dist/repo/projects.js.map +1 -0
- package/dist/repo/suppressions.d.ts +8 -0
- package/dist/repo/suppressions.d.ts.map +1 -0
- package/dist/repo/suppressions.js +16 -0
- package/dist/repo/suppressions.js.map +1 -0
- package/dist/repo/trace-read.d.ts +28 -0
- package/dist/repo/trace-read.d.ts.map +1 -0
- package/dist/repo/trace-read.js +76 -0
- package/dist/repo/trace-read.js.map +1 -0
- package/dist/repo/trace-write.d.ts +9 -0
- package/dist/repo/trace-write.d.ts.map +1 -0
- package/dist/repo/trace-write.js +82 -0
- package/dist/repo/trace-write.js.map +1 -0
- package/dist/repo/users.d.ts +13 -0
- package/dist/repo/users.d.ts.map +1 -0
- package/dist/repo/users.js +7 -0
- package/dist/repo/users.js.map +1 -0
- package/dist/routes/alerts.d.ts +4 -0
- package/dist/routes/alerts.d.ts.map +1 -0
- package/dist/routes/alerts.js +116 -0
- package/dist/routes/alerts.js.map +1 -0
- package/dist/routes/data.d.ts +7 -0
- package/dist/routes/data.d.ts.map +1 -0
- package/dist/routes/data.js +74 -0
- package/dist/routes/data.js.map +1 -0
- package/dist/routes/index.d.ts +5 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +25 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/ingest.d.ts +8 -0
- package/dist/routes/ingest.d.ts.map +1 -0
- package/dist/routes/ingest.js +38 -0
- package/dist/routes/ingest.js.map +1 -0
- package/dist/routes/mcp.d.ts +4 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +52 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/me.d.ts +4 -0
- package/dist/routes/me.d.ts.map +1 -0
- package/dist/routes/me.js +37 -0
- package/dist/routes/me.js.map +1 -0
- package/dist/routes/metrics.d.ts +13 -0
- package/dist/routes/metrics.d.ts.map +1 -0
- package/dist/routes/metrics.js +29 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/models.d.ts +4 -0
- package/dist/routes/models.d.ts.map +1 -0
- package/dist/routes/models.js +71 -0
- package/dist/routes/models.js.map +1 -0
- package/dist/routes/notify.d.ts +4 -0
- package/dist/routes/notify.d.ts.map +1 -0
- package/dist/routes/notify.js +55 -0
- package/dist/routes/notify.js.map +1 -0
- package/dist/routes/orgs.d.ts +4 -0
- package/dist/routes/orgs.d.ts.map +1 -0
- package/dist/routes/orgs.js +73 -0
- package/dist/routes/orgs.js.map +1 -0
- package/dist/routes/projects.d.ts +4 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +55 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/shares.d.ts +11 -0
- package/dist/routes/shares.d.ts.map +1 -0
- package/dist/routes/shares.js +61 -0
- package/dist/routes/shares.js.map +1 -0
- package/dist/scale-exec.d.ts +11 -0
- package/dist/scale-exec.d.ts.map +1 -0
- package/dist/scale-exec.js +26 -0
- package/dist/scale-exec.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +126 -0
- package/dist/server.js.map +1 -0
- package/dist/settle.d.ts +26 -0
- package/dist/settle.d.ts.map +1 -0
- package/dist/settle.js +85 -0
- package/dist/settle.js.map +1 -0
- package/package.json +76 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WizeWorks LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wizeworks/lens-cloud
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The cloud service of [WizeLens](https://github.com/brandonkorous/wizelens): the full dashboard for many teams, with accounts,
|
|
4
|
+
projects, 30 days of history computed on the server, alerts, share links, and AI access through read keys.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
# Try it on your machine (Postgres in-process, data in ./.wizelens-cloud)
|
|
8
|
+
npx @wizeworks/lens-cloud
|
|
9
|
+
|
|
10
|
+
# For a team: Postgres in Docker
|
|
11
|
+
POSTGRES_PASSWORD=<a long secret> docker compose -f deploy/docker-compose.cloud.yml up -d
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The cloud prints a one-time setup link while it has no account. Open it and create the first account. Make an ingest key on the
|
|
15
|
+
Team page, then on each app:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
WIZELENS_HUB=http://localhost:5970
|
|
19
|
+
WIZELENS_HUB_TOKEN=wl_ik_...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Options
|
|
23
|
+
|
|
24
|
+
| Flag | Environment variable | Default |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `--database <url>` | `DATABASE_URL` | `pglite:./.wizelens-cloud` (`postgres://…`, `pglite:<folder>`, or `memory`) |
|
|
27
|
+
| `--port <n>` | `PORT` | `5970` |
|
|
28
|
+
| `--host <addr>` | `WIZELENS_CLOUD_HOST` | `0.0.0.0` |
|
|
29
|
+
| `--public-url <url>` | `WIZELENS_CLOUD_PUBLIC_URL` | the request's own address |
|
|
30
|
+
| `--signup <invite\|open>` | `WIZELENS_CLOUD_SIGNUP` | `invite` |
|
|
31
|
+
| `--retention-days <n>` | `WIZELENS_CLOUD_RETENTION_DAYS` | `30` |
|
|
32
|
+
| `--allow-private-webhooks` | `WIZELENS_CLOUD_ALLOW_PRIVATE_WEBHOOKS=1` | off |
|
|
33
|
+
| `--auth-secret <text>` | `WIZELENS_CLOUD_AUTH_SECRET` | required with Postgres (32+ random characters; `2:new,1:old` rotates) |
|
|
34
|
+
| `--resend-api-key <key>` | `WIZELENS_CLOUD_RESEND_API_KEY` | none: mail through the [Resend](https://resend.com) API (`re_…`) |
|
|
35
|
+
| `--resend-webhook-secret <s>` | `WIZELENS_CLOUD_RESEND_WEBHOOK_SECRET` | none: the `whsec_…` secret of the Resend webhook (see below) |
|
|
36
|
+
| `--smtp-url <url>` | `WIZELENS_CLOUD_SMTP_URL` | none: or mail over SMTP (`smtp://user:pass@host:587` or `smtps://…:465`) |
|
|
37
|
+
| `--mail-from <address>` | `WIZELENS_CLOUD_MAIL_FROM` | `WizeLens <no-reply@<public host>>` |
|
|
38
|
+
| `--trust-proxy` | `WIZELENS_CLOUD_TRUST_PROXY=1` | off (client address from the last `X-Forwarded-For` entry) |
|
|
39
|
+
| `--setup-token <text>` | `WIZELENS_CLOUD_SETUP_TOKEN` | random, printed at start while no account exists |
|
|
40
|
+
| `--[no-]check-pwned-passwords` | `WIZELENS_CLOUD_CHECK_PWNED=1\|0` | on with Postgres, off with PGlite |
|
|
41
|
+
| `--sso-origins <list>` | `WIZELENS_CLOUD_SSO_ORIGINS` | none: identity provider origins for OIDC single sign-on, comma separated (`https://acme.okta.com`) |
|
|
42
|
+
|
|
43
|
+
Put it behind https, and set `--public-url` to the https address: cookies are then Secure, and passkeys and email links use it.
|
|
44
|
+
Sending mail needs `--public-url`. Without `--resend-api-key` or `--smtp-url`, nobody gets email: reset and invite links are in
|
|
45
|
+
the log, and email addresses are not confirmed. PGlite is for trials and tests; use Postgres for real teams.
|
|
46
|
+
|
|
47
|
+
## Email
|
|
48
|
+
|
|
49
|
+
Use Resend or SMTP, not both. With Resend, each email is sent with an idempotency key and tried up to 4 times on 429 and 5xx answers.
|
|
50
|
+
Add a Resend webhook to `<public-url>/_wizelens/api/mail/resend` with the events `email.bounced` and `email.complained`, and set
|
|
51
|
+
`--resend-webhook-secret` to its signing secret. The cloud checks the signature, then keeps a suppression list: after a hard bounce
|
|
52
|
+
no mail goes to that address, and after a spam complaint only mail the person asked for (confirm, reset, change, delete) goes
|
|
53
|
+
there. Confirming the address again by link clears it.
|
|
54
|
+
|
|
55
|
+
What the cloud sends, beyond sign-in links and invites:
|
|
56
|
+
|
|
57
|
+
- **Security notices**, always, to the person: password changed, email changed (to the old address), two-step sign-in on or off,
|
|
58
|
+
passkey added or removed, new backup codes, and a sign-in on a browser and system the account never used.
|
|
59
|
+
- **Team changes**, to owners and admins: members invited, joined, left, removed, or given a new role (that member hears too), keys,
|
|
60
|
+
single sign-on, projects, share links, and settings. One email per batch, without the reader's own changes. A deleted
|
|
61
|
+
organization is told to every member.
|
|
62
|
+
- **Project health**, to owners and admins: a project that sends data in most hours went quiet for 2 hours, and when it is back.
|
|
63
|
+
- **Alerts**, from alert rules.
|
|
64
|
+
- **Digests**, per person and project: daily, weekly (Mondays), and monthly (the 1st), at the person's hour and time zone. Weekly
|
|
65
|
+
is on by default. Each has requests, error rate, average and p95 time, SQL queries, alerts fired, top errors, the slowest
|
|
66
|
+
endpoints, and insights. Projects with no traffic get none.
|
|
67
|
+
|
|
68
|
+
Team, health, and digest emails carry a one-click stop link (`List-Unsubscribe`, RFC 8058). People pick all of it on the Account
|
|
69
|
+
page. `pnpm --filter @wizeworks/lens-cloud email:preview <folder>` writes every email to HTML and text.
|
|
70
|
+
|
|
71
|
+
## Accounts
|
|
72
|
+
|
|
73
|
+
Sign-in is [Better Auth](https://better-auth.com) (pinned to one version): email and password, passkeys, two-step codes from an
|
|
74
|
+
authenticator app with backup codes, password reset and email confirm by link, sessions per device (sign out one or all),
|
|
75
|
+
organizations with owner, admin, member, and viewer roles, email invites, single sign-on per organization (OIDC or SAML, after a
|
|
76
|
+
DNS proof of the email domain; Team page), and account deletion. Sign-in tries are limited per
|
|
77
|
+
client address across all instances. Existing accounts keep working after the upgrade; everyone signs in once more. See
|
|
78
|
+
`docs/CONTRACT.md`, "Tenancy and access".
|
|
79
|
+
|
|
80
|
+
Check a running cloud end to end with `node scripts/cloud-smoke.mjs <url> <setup token>` from the repo (a fresh database, no SMTP).
|
|
81
|
+
|
|
82
|
+
## Stats
|
|
83
|
+
|
|
84
|
+
Pages are computed from stats tables, never from a sample of traces: per-trace facts (request, trace, log, and slow span rows) and
|
|
85
|
+
rollups in 10 s, 5 min, and 1 h buckets, so 30-day pages on busy projects answer in well under a second. A background job settles
|
|
86
|
+
traces a few seconds after their last span (every 3 s, one instance at a time through a Postgres advisory lock); pages say
|
|
87
|
+
"Preparing stats" while traces wait, for example right after an upgrade, when existing traces are backfilled newest first.
|
|
88
|
+
Alerts on traces read the same tables. See `docs/CONTRACT.md`, "Stats tables".
|
|
89
|
+
|
|
90
|
+
## Metrics
|
|
91
|
+
|
|
92
|
+
Apps send OpenTelemetry metrics to `POST /v1/metrics` with the ingest key (JSON or protobuf; gauges, sums, histograms,
|
|
93
|
+
exponential histograms, and summaries). WizeLens add-ons send theirs in their batches. The Metrics page reads
|
|
94
|
+
`GET /_wizelens/api/models/metrics?project=&range=&env=&service=&metric=&group=&stat=&q=` (member or read key), computed from
|
|
95
|
+
metric rollups (per series, in 10 s, 5 min, and 1 h buckets, written as points arrive), so every range reads all of its points.
|
|
96
|
+
Points and rollups follow the organization's retention. Alert rules can watch
|
|
97
|
+
one metric: `{ "metric": "metric", "metricName": "process.cpu.utilization", "metricStat": "avg", "metricService": "", ... }`, with
|
|
98
|
+
`metricStat` one of `last`, `avg`, `max`, `p95`, `p99`, `rate`, and the threshold in the metric's own unit (seconds, bytes, or
|
|
99
|
+
0 to 1 for ratios). Read keys get `list_metrics` and `get_metric` over MCP.
|
|
100
|
+
|
|
101
|
+
## In code
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { createCloud } from "@wizeworks/lens-cloud";
|
|
105
|
+
|
|
106
|
+
const cloud = await createCloud({
|
|
107
|
+
database: process.env.DATABASE_URL,
|
|
108
|
+
publicUrl: "https://wizelens.example.com",
|
|
109
|
+
authSecret: process.env.WIZELENS_CLOUD_AUTH_SECRET,
|
|
110
|
+
resendApiKey: process.env.WIZELENS_CLOUD_RESEND_API_KEY,
|
|
111
|
+
resendWebhookSecret: process.env.WIZELENS_CLOUD_RESEND_WEBHOOK_SECRET,
|
|
112
|
+
});
|
|
113
|
+
console.log(await cloud.listen());
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The API, roles, keys, and storage are in [CONTRACT.md](https://github.com/brandonkorous/wizelens/blob/main/docs/CONTRACT.md#cloud-v07-phase-3). MIT licensed.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CloudApp } from "../app.js";
|
|
2
|
+
import { type AlertRule } from "../repo/alerts.js";
|
|
3
|
+
import { type Notice } from "./notify.js";
|
|
4
|
+
/** Send a notice and keep it as an event. */
|
|
5
|
+
export declare function notify(app: CloudApp, rule: AlertRule, state: Notice["state"], value: number | null, now: number): Promise<string>;
|
|
6
|
+
/** Check one rule. It notifies only when the state changes: ok to firing, or firing to ok. */
|
|
7
|
+
export declare function evaluateRule(app: CloudApp, rule: AlertRule, now: number): Promise<void>;
|
|
8
|
+
/** Check every enabled rule once. One failing rule does not stop the others. */
|
|
9
|
+
export declare function evaluateAll(app: CloudApp): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/alerts/evaluate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAY,KAAK,SAAS,EAAgC,MAAM,mBAAmB,CAAC;AAK3F,OAAO,EAAW,KAAK,MAAM,EAAe,MAAM,aAAa,CAAC;AAsDhE,6CAA6C;AAC7C,wBAAsB,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvI;AAED,8FAA8F;AAC9F,wBAAsB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7F;AAED,gFAAgF;AAChF,wBAAsB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9D"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { alertBreached, alignNow, alertContext, alertMessage, alertPage, computePage, metricAlertMessage, metricAlertUnit, metricAlertValueOf } from "@wizeworks/lens-core";
|
|
2
|
+
import { addEvent, enabledRules, saveEvaluation } from "../repo/alerts.js";
|
|
3
|
+
import { loadMetricAlertData } from "../repo/metric-read.js";
|
|
4
|
+
import { cloudExecutor, projectRetentionMs } from "../scale-exec.js";
|
|
5
|
+
import { alertMail } from "../mail/templates.js";
|
|
6
|
+
import { memberEmails } from "../repo/users.js";
|
|
7
|
+
import { deliver } from "./notify.js";
|
|
8
|
+
/** Names for the notice: project and the dashboard link. */
|
|
9
|
+
async function noticeOf(app, rule, state, value, now) {
|
|
10
|
+
const [p] = await app.db.query("SELECT name FROM projects WHERE id = $1", [rule.projectId]);
|
|
11
|
+
const base = app.baseUrl();
|
|
12
|
+
return {
|
|
13
|
+
rule: rule.name,
|
|
14
|
+
project: p?.name ?? rule.projectId,
|
|
15
|
+
state,
|
|
16
|
+
message: await messageOf(app, rule, state, value, now),
|
|
17
|
+
value,
|
|
18
|
+
threshold: rule.threshold,
|
|
19
|
+
timeMs: now,
|
|
20
|
+
url: `${base}/_wizelens/#/alerts?project=${rule.projectId}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** One OpenTelemetry metric over the rule's window (from its rollups), and a sample series for its unit. */
|
|
24
|
+
async function measureMetric(app, rule, now) {
|
|
25
|
+
const at = alignNow(now);
|
|
26
|
+
const q = { projectId: rule.projectId, env: rule.env || undefined, service: rule.metricService || undefined };
|
|
27
|
+
const data = await loadMetricAlertData(app.db, q, rule.metricName, { from: at - rule.windowMs, to: at }, at);
|
|
28
|
+
const stat = rule.metricStat || "avg";
|
|
29
|
+
return { value: metricAlertValueOf(data, stat), sample: data.series[0], stat };
|
|
30
|
+
}
|
|
31
|
+
async function messageOf(app, rule, state, value, now) {
|
|
32
|
+
if (rule.metric !== "metric")
|
|
33
|
+
return alertMessage(rule.metric, rule.op, value, rule.threshold, state);
|
|
34
|
+
const { sample, stat } = await measureMetric(app, rule, now);
|
|
35
|
+
const unit = sample ? metricAlertUnit(sample, stat) : "";
|
|
36
|
+
return metricAlertMessage(rule.metricName, stat, rule.op, value, rule.threshold, unit, state);
|
|
37
|
+
}
|
|
38
|
+
/** The metric over the rule's window: from the stats tables, or from the metric's points. */
|
|
39
|
+
async function measure(app, rule, now) {
|
|
40
|
+
if (rule.metric === "metric")
|
|
41
|
+
return (await measureMetric(app, rule, now)).value;
|
|
42
|
+
const retentionMs = await projectRetentionMs(app.db, rule.projectId, app.config.retentionDays);
|
|
43
|
+
return computePage(alertPage(rule.metric), alertContext(rule.windowMs, alignNow(now)), cloudExecutor(app.db, rule.projectId, retentionMs, rule.env || undefined));
|
|
44
|
+
}
|
|
45
|
+
/** Webhooks, Slack, and email. Email goes only to addresses that still belong to confirmed members of the organization. */
|
|
46
|
+
async function outboxOf(app, projectId) {
|
|
47
|
+
const [p] = await app.db.query("SELECT org_id FROM projects WHERE id = $1", [projectId]);
|
|
48
|
+
const members = new Set(p ? await memberEmails(app.db, p.org_id, app.mailer.live) : []);
|
|
49
|
+
const email = (to, n) => {
|
|
50
|
+
const ok = to.filter((e) => members.has(e));
|
|
51
|
+
for (const addr of ok)
|
|
52
|
+
app.mailer.send(alertMail(addr, n));
|
|
53
|
+
const note = app.mailer.live ? `sent to ${ok.length}` : "mail is not set up (see the cloud log)";
|
|
54
|
+
return `email: ${note}${ok.length < to.length ? `, ${to.length - ok.length} no longer members` : ""}`;
|
|
55
|
+
};
|
|
56
|
+
return { http: app.config.fetch, allowPrivate: app.config.allowPrivateWebhooks, email };
|
|
57
|
+
}
|
|
58
|
+
/** Send a notice and keep it as an event. */
|
|
59
|
+
export async function notify(app, rule, state, value, now) {
|
|
60
|
+
const notice = await noticeOf(app, rule, state, value, now);
|
|
61
|
+
const delivery = await deliver(rule.channels, notice, await outboxOf(app, rule.projectId));
|
|
62
|
+
await addEvent(app.db, rule, { kind: state, value, message: notice.message, timeMs: now, delivery });
|
|
63
|
+
return delivery;
|
|
64
|
+
}
|
|
65
|
+
/** Check one rule. It notifies only when the state changes: ok to firing, or firing to ok. */
|
|
66
|
+
export async function evaluateRule(app, rule, now) {
|
|
67
|
+
const value = await measure(app, rule, now);
|
|
68
|
+
const firing = rule.metric === "metric" ? value !== null && (rule.op === ">" ? value > rule.threshold : value < rule.threshold) : alertBreached(rule.metric, rule.op, value, rule.threshold);
|
|
69
|
+
const next = firing ? "firing" : "ok";
|
|
70
|
+
await saveEvaluation(app.db, rule.id, next, value, now);
|
|
71
|
+
if (next !== rule.state)
|
|
72
|
+
await notify(app, rule, firing ? "fired" : "resolved", value, now);
|
|
73
|
+
}
|
|
74
|
+
/** Check every enabled rule once. One failing rule does not stop the others. */
|
|
75
|
+
export async function evaluateAll(app) {
|
|
76
|
+
const now = app.config.now();
|
|
77
|
+
for (const rule of await enabledRules(app.db)) {
|
|
78
|
+
await evaluateRule(app, rule, now).catch((err) => console.error(`[wizelens-cloud] alert ${rule.id} failed:`, err));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/alerts/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Channel } from "../repo/alerts.js";
|
|
2
|
+
export interface Notice {
|
|
3
|
+
rule: string;
|
|
4
|
+
project: string;
|
|
5
|
+
state: "fired" | "resolved" | "test";
|
|
6
|
+
message: string;
|
|
7
|
+
value: number | null;
|
|
8
|
+
threshold: number;
|
|
9
|
+
timeMs: number;
|
|
10
|
+
/** Link to the project in the dashboard. */
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
/** How notices leave: HTTP for webhooks and Slack, and a mail function that sends to the members still allowed and says what it did. */
|
|
14
|
+
export interface Outbox {
|
|
15
|
+
http: typeof fetch;
|
|
16
|
+
allowPrivate: boolean;
|
|
17
|
+
email: (to: readonly string[], n: Notice) => string;
|
|
18
|
+
}
|
|
19
|
+
/** A webhook may not point into the service's own network (cloud metadata, databases). Self-hosts can allow it. */
|
|
20
|
+
export declare function checkTarget(url: string, allowPrivate: boolean): Promise<string | null>;
|
|
21
|
+
/** The notice in words, for Slack and email. */
|
|
22
|
+
export declare function noticeLines(n: Notice): {
|
|
23
|
+
subject: string;
|
|
24
|
+
lines: string[];
|
|
25
|
+
};
|
|
26
|
+
/** Send to every channel. Returns one line per channel, for the delivery note of the event. */
|
|
27
|
+
export declare function deliver(channels: readonly Channel[], n: Notice, out: Outbox): Promise<string>;
|
|
28
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/alerts/notify.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;CACb;AAID,wIAAwI;AACxI,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,OAAO,KAAK,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACrD;AAKD,mHAAmH;AACnH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAY5F;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAG3E;AAoBD,+FAA+F;AAC/F,wBAAsB,OAAO,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGnG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
const PRIVATE_V4 = [/^10\./, /^127\./, /^169\.254\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./, /^0\./, /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./];
|
|
4
|
+
const isPrivate = (ip) => (isIP(ip) === 6 ? /^(::1|fc|fd|fe80|::ffff:(10|127|169\.254|192\.168)\.)/i.test(ip) || ip === "::" : PRIVATE_V4.some((r) => r.test(ip)));
|
|
5
|
+
/** A webhook may not point into the service's own network (cloud metadata, databases). Self-hosts can allow it. */
|
|
6
|
+
export async function checkTarget(url, allowPrivate) {
|
|
7
|
+
let u;
|
|
8
|
+
try {
|
|
9
|
+
u = new URL(url);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return "Not a valid URL.";
|
|
13
|
+
}
|
|
14
|
+
if (u.protocol !== "https:" && !(allowPrivate && u.protocol === "http:"))
|
|
15
|
+
return "Use an https:// URL.";
|
|
16
|
+
if (allowPrivate)
|
|
17
|
+
return null;
|
|
18
|
+
const addresses = isIP(u.hostname) ? [u.hostname] : (await lookup(u.hostname, { all: true }).catch(() => [])).map((a) => a.address);
|
|
19
|
+
if (addresses.length === 0)
|
|
20
|
+
return "The host name does not resolve.";
|
|
21
|
+
return addresses.some(isPrivate) ? "Webhooks cannot point at private or local addresses." : null;
|
|
22
|
+
}
|
|
23
|
+
/** The notice in words, for Slack and email. */
|
|
24
|
+
export function noticeLines(n) {
|
|
25
|
+
const verb = n.state === "fired" ? "Alert" : n.state === "resolved" ? "Resolved" : "Test";
|
|
26
|
+
return { subject: `${verb}: ${n.rule} (${n.project})`, lines: [`${verb}: ${n.rule} in ${n.project}.`, n.message] };
|
|
27
|
+
}
|
|
28
|
+
function bodyOf(channel, n) {
|
|
29
|
+
if (channel.kind === "webhook")
|
|
30
|
+
return n;
|
|
31
|
+
const icon = n.state === "fired" ? ":rotating_light:" : n.state === "resolved" ? ":white_check_mark:" : ":bell:";
|
|
32
|
+
const { subject } = noticeLines(n);
|
|
33
|
+
return { text: `${icon} *${subject}*\n${n.message}\n<${n.url}|Open WizeLens>` };
|
|
34
|
+
}
|
|
35
|
+
async function post(c, n, out) {
|
|
36
|
+
const problem = await checkTarget(c.url, out.allowPrivate);
|
|
37
|
+
if (problem)
|
|
38
|
+
return `${c.kind}: ${problem}`;
|
|
39
|
+
try {
|
|
40
|
+
const res = await out.http(c.url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(bodyOf(c, n)), signal: AbortSignal.timeout(5000), redirect: "error" });
|
|
41
|
+
return `${c.kind}: ${res.ok ? "sent" : `HTTP ${res.status}`}`;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return `${c.kind}: ${err instanceof Error ? err.message : "failed"}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Send to every channel. Returns one line per channel, for the delivery note of the event. */
|
|
48
|
+
export async function deliver(channels, n, out) {
|
|
49
|
+
const results = await Promise.all(channels.map(async (c) => (c.kind === "email" ? out.email(c.to, n) : post(c, n, out))));
|
|
50
|
+
return results.join("; ") || "no channels";
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/alerts/notify.ts"],"names":[],"mappings":"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"}
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { WizeAuth } from "./auth/auth.js";
|
|
3
|
+
import type { AuthSecret } from "./auth/secret.js";
|
|
4
|
+
import type { CloudConfig } from "./config.js";
|
|
5
|
+
import type { Db } from "./db/db.js";
|
|
6
|
+
import type { RouteCtx } from "./http/router.js";
|
|
7
|
+
import type { Mailer } from "./mail/mailer.js";
|
|
8
|
+
/** Everything a request handler needs. */
|
|
9
|
+
export interface CloudApp {
|
|
10
|
+
db: Db;
|
|
11
|
+
config: CloudConfig;
|
|
12
|
+
version: string;
|
|
13
|
+
startedMs: number;
|
|
14
|
+
mailer: Mailer;
|
|
15
|
+
/** The auth secrets, newest first, for signed mail links. */
|
|
16
|
+
secrets: readonly AuthSecret[];
|
|
17
|
+
/** Better Auth. Ready once the cloud listens (its address goes into links and cookie checks). */
|
|
18
|
+
readonly auth: WizeAuth;
|
|
19
|
+
/** The address for links: the public URL, or the address the cloud listens on. */
|
|
20
|
+
baseUrl(req?: IncomingMessage): string;
|
|
21
|
+
}
|
|
22
|
+
export interface Ctx extends RouteCtx {
|
|
23
|
+
app: CloudApp;
|
|
24
|
+
}
|
|
25
|
+
export type { IncomingMessage, ServerResponse };
|
|
26
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,0CAA0C;AAC1C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,kFAAkF;IAClF,OAAO,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,GAAI,SAAQ,QAAQ;IACnC,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/app.js
ADDED
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type BetterAuthOptions } from "better-auth";
|
|
2
|
+
import type { AuthDeps } from "./deps.js";
|
|
3
|
+
/** The parts of Better Auth the cloud calls itself; the dashboard reaches the rest through `api/auth/*`. */
|
|
4
|
+
export interface WizeAuth {
|
|
5
|
+
handler: (request: Request) => Promise<Response>;
|
|
6
|
+
options: BetterAuthOptions;
|
|
7
|
+
api: {
|
|
8
|
+
getSession(a: {
|
|
9
|
+
headers: Headers;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
user: SessionUser;
|
|
12
|
+
session: {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
} | null>;
|
|
16
|
+
verifyPassword(a: {
|
|
17
|
+
body: {
|
|
18
|
+
password: string;
|
|
19
|
+
};
|
|
20
|
+
headers: Headers;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
createOrganization(a: {
|
|
23
|
+
body: {
|
|
24
|
+
name: string;
|
|
25
|
+
slug: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
};
|
|
28
|
+
}): Promise<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
slug: string;
|
|
32
|
+
} | null>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
interface SessionUser {
|
|
36
|
+
id: string;
|
|
37
|
+
email: string;
|
|
38
|
+
name: string;
|
|
39
|
+
emailVerified: boolean;
|
|
40
|
+
twoFactorEnabled?: boolean | null;
|
|
41
|
+
}
|
|
42
|
+
/** Better Auth for the cloud: accounts, sessions, 2FA, passkeys, SSO, organizations, members, and invites (docs/CONTRACT.md, "Tenancy and access"). */
|
|
43
|
+
export declare function createAuth(deps: AuthDeps): WizeAuth;
|
|
44
|
+
/** A new organization with `userId` as owner, the cloud's default retention, a first project, and an audit line. */
|
|
45
|
+
export declare function createOrgFor(auth: WizeAuth, deps: Pick<AuthDeps, "db" | "retentionDays">, userId: string, name: string): Promise<{
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
slug: string;
|
|
49
|
+
}>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AA4C1C,4GAA4G;AAC5G,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,GAAG,EAAE;QACH,UAAU,CAAC,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,IAAI,EAAE,WAAW,CAAC;YAAC,OAAO,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;QACpG,cAAc,CAAC,CAAC,EAAE;YAAE,IAAI,EAAE;gBAAE,QAAQ,EAAE,MAAM,CAAA;aAAE,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACtF,kBAAkB,CAAC,CAAC,EAAE;YAAE,IAAI,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;KAC7I,CAAC;CACH;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAED,uJAAuJ;AACvJ,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAGnD;AAED,oHAAoH;AACpH,wBAAsB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAWlL"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
import { audit } from "../repo/audit.js";
|
|
3
|
+
import { freeSlug } from "../repo/orgs.js";
|
|
4
|
+
import { createProject } from "../repo/projects.js";
|
|
5
|
+
import { beforeDeleteUser } from "./delete-user.js";
|
|
6
|
+
import { authHooks } from "./hooks.js";
|
|
7
|
+
import { advancedOptions, AUTH_BASE_PATH, passwordAuth, rateLimitOptions, sessionOptions, userOptions } from "./options.js";
|
|
8
|
+
import { authPlugins } from "./plugins.js";
|
|
9
|
+
import { securityDbHooks } from "./security-notices.js";
|
|
10
|
+
import { afterSignup, confirmSsoEmail } from "./signup.js";
|
|
11
|
+
const ALWAYS_OFF = ["/sign-in/social", "/link-social", "/get-access-token", "/refresh-token", "/account-info", "/delete-user/callback", "/update-session"];
|
|
12
|
+
/** Invite ids are link secrets: no route lists them (managers see them on our Team page), nor, without real mail, by "their" email. */
|
|
13
|
+
const disabledPaths = (deps) => [
|
|
14
|
+
...ALWAYS_OFF,
|
|
15
|
+
"/organization/list-invitations",
|
|
16
|
+
"/organization/get-full-organization",
|
|
17
|
+
...(deps.mailer.live ? [] : ["/organization/list-user-invitations"]),
|
|
18
|
+
];
|
|
19
|
+
/** Sign-up (first organization, SSO email) and security notices (new device, two-step sign-in, changed email). */
|
|
20
|
+
function databaseHooksOf(deps, createOrg) {
|
|
21
|
+
const notices = securityDbHooks(deps);
|
|
22
|
+
return { session: notices.session, user: { create: { before: confirmSsoEmail, after: afterSignup(deps, createOrg) }, update: notices.user?.update } };
|
|
23
|
+
}
|
|
24
|
+
function build(deps, createOrg) {
|
|
25
|
+
return betterAuth({
|
|
26
|
+
appName: "WizeLens",
|
|
27
|
+
baseURL: deps.publicUrl,
|
|
28
|
+
basePath: AUTH_BASE_PATH,
|
|
29
|
+
secrets: deps.secrets,
|
|
30
|
+
trustedOrigins: [...deps.trustedOrigins, ...deps.ssoOrigins],
|
|
31
|
+
telemetry: { enabled: false },
|
|
32
|
+
disabledPaths: disabledPaths(deps),
|
|
33
|
+
database: deps.db.authDatabase,
|
|
34
|
+
user: userOptions(deps, beforeDeleteUser(deps.db)),
|
|
35
|
+
...passwordAuth(deps),
|
|
36
|
+
...sessionOptions(),
|
|
37
|
+
rateLimit: rateLimitOptions(),
|
|
38
|
+
advanced: advancedOptions(deps),
|
|
39
|
+
databaseHooks: databaseHooksOf(deps, createOrg),
|
|
40
|
+
hooks: authHooks(deps),
|
|
41
|
+
plugins: authPlugins(deps),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Better Auth for the cloud: accounts, sessions, 2FA, passkeys, SSO, organizations, members, and invites (docs/CONTRACT.md, "Tenancy and access"). */
|
|
45
|
+
export function createAuth(deps) {
|
|
46
|
+
const auth = build(deps, (userId, name) => createOrgFor(auth, deps, userId, name));
|
|
47
|
+
return auth;
|
|
48
|
+
}
|
|
49
|
+
/** A new organization with `userId` as owner, the cloud's default retention, a first project, and an audit line. */
|
|
50
|
+
export async function createOrgFor(auth, deps, userId, name) {
|
|
51
|
+
const clean = name.trim().slice(0, 100) || "My organization";
|
|
52
|
+
const org = await auth.api.createOrganization({ body: { name: clean, slug: await freeSlug(deps.db, clean), userId } });
|
|
53
|
+
if (!org)
|
|
54
|
+
throw new Error("Could not create the organization.");
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
await deps.db.tx(async (q) => {
|
|
57
|
+
await q.query("UPDATE orgs SET retention_days = $1 WHERE id = $2", [deps.retentionDays, org.id]);
|
|
58
|
+
await createProject(q, org.id, "My app", now);
|
|
59
|
+
await audit(q, org.id, `user:${userId}`, "org.create", { name: clean }, now);
|
|
60
|
+
});
|
|
61
|
+
return { id: org.id, name: org.name, slug: org.slug };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { WizeAuth } from "./auth.js";
|
|
3
|
+
/** The caller's address: the socket, or (behind a proxy we run) the last address that proxy added to X-Forwarded-For. */
|
|
4
|
+
export declare function clientIp(req: IncomingMessage, trustProxy: boolean): string;
|
|
5
|
+
/** Better Auth below `/_wizelens/api/auth/`: our dashboard header on changes, a body limit, and our own client-address header. */
|
|
6
|
+
export declare function authHandler(auth: WizeAuth, trustProxy: boolean): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
7
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/auth/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAU1C,yHAAyH;AACzH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAI1E;AAOD,kIAAkI;AAClI,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,SAE1C,eAAe,OAAO,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CAaxE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { toNodeHandler } from "better-auth/node";
|
|
2
|
+
import { AUTH_BASE_PATH, CLIENT_IP_HEADER } from "./options.js";
|
|
3
|
+
const SAFE = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
4
|
+
/** Paths an outside party posts to (sign-in providers), which cannot send the dashboard header. None are on yet; SSO adds them. */
|
|
5
|
+
const EXTERNAL = [/^\/callback\/[^/]+$/, /^\/sso\/callback(\/[^/]+)?$/, /^\/sso\/saml2\/sp\/(acs|slo)\/[^/]+$/];
|
|
6
|
+
const IP_HEADERS = ["x-forwarded-for", "x-real-ip", "cf-connecting-ip", "true-client-ip", "forwarded", CLIENT_IP_HEADER];
|
|
7
|
+
const MAX_BODY = 64 * 1024;
|
|
8
|
+
const MAX_EXTERNAL_BODY = 1024 * 1024;
|
|
9
|
+
/** The caller's address: the socket, or (behind a proxy we run) the last address that proxy added to X-Forwarded-For. */
|
|
10
|
+
export function clientIp(req, trustProxy) {
|
|
11
|
+
const forwarded = String(req.headers["x-forwarded-for"] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
12
|
+
const ip = trustProxy && forwarded.length ? forwarded[forwarded.length - 1] : (req.socket.remoteAddress ?? "");
|
|
13
|
+
return ip.replace(/^::ffff:/, "");
|
|
14
|
+
}
|
|
15
|
+
function deny(res, status, message) {
|
|
16
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
17
|
+
res.end(JSON.stringify({ error: message, message }));
|
|
18
|
+
}
|
|
19
|
+
/** Better Auth below `/_wizelens/api/auth/`: our dashboard header on changes, a body limit, and our own client-address header. */
|
|
20
|
+
export function authHandler(auth, trustProxy) {
|
|
21
|
+
const handler = toNodeHandler(auth);
|
|
22
|
+
return async (req, res) => {
|
|
23
|
+
const path = new URL(req.url ?? "/", "http://cloud.local").pathname.slice(AUTH_BASE_PATH.length);
|
|
24
|
+
const external = EXTERNAL.some((r) => r.test(path));
|
|
25
|
+
if (!SAFE.has(req.method ?? "GET")) {
|
|
26
|
+
if (!external && req.headers["x-wizelens-client"] !== "dashboard")
|
|
27
|
+
return deny(res, 403, "Missing the X-WizeLens-Client header.");
|
|
28
|
+
const length = Number(req.headers["content-length"] ?? 0);
|
|
29
|
+
if (req.headers["transfer-encoding"] || length > (external ? MAX_EXTERNAL_BODY : MAX_BODY))
|
|
30
|
+
return deny(res, 413, "The body is too large.");
|
|
31
|
+
}
|
|
32
|
+
const ip = clientIp(req, trustProxy);
|
|
33
|
+
for (const h of IP_HEADERS)
|
|
34
|
+
delete req.headers[h];
|
|
35
|
+
req.headers[CLIENT_IP_HEADER] = ip;
|
|
36
|
+
await handler(req, res);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/auth/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Kinds of secret tokens. The prefix tells a person what a token is for. */
|
|
2
|
+
export declare const TOKEN_PREFIX: {
|
|
3
|
+
readonly ingest: "wl_ik_";
|
|
4
|
+
readonly read: "wl_rk_";
|
|
5
|
+
readonly share: "wl_sh_";
|
|
6
|
+
};
|
|
7
|
+
export type TokenKind = keyof typeof TOKEN_PREFIX;
|
|
8
|
+
/** A new random token. Only its hash is stored. */
|
|
9
|
+
export declare const newToken: (kind: TokenKind) => string;
|
|
10
|
+
export declare const hashToken: (token: string) => string;
|
|
11
|
+
export declare const newId: () => string;
|
|
12
|
+
/** A URL-safe short name from free text, for example "My Shop!" -> "my-shop". */
|
|
13
|
+
export declare const slugOf: (text: string) => string;
|
|
14
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,eAAO,MAAM,YAAY;aAAK,MAAM,EAAE,QAAQ;aAAE,IAAI,EAAE,QAAQ;aAAE,KAAK,EAAE,QAAQ;CAAW,CAAC;AAC3F,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAC;AAElD,mDAAmD;AACnD,eAAO,MAAM,QAAQ,SAAU,SAAS,KAAG,MAAyE,CAAC;AAErH,eAAO,MAAM,SAAS,UAAW,MAAM,KAAG,MAA0D,CAAC;AAErG,eAAO,MAAM,KAAK,QAAO,MAAsB,CAAC;AAEhD,iFAAiF;AACjF,eAAO,MAAM,MAAM,SAAU,MAAM,KAAG,MAMX,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
// Our own secrets: data-plane keys and share links. Passwords, sessions, and invites are Better Auth's.
|
|
3
|
+
/** Kinds of secret tokens. The prefix tells a person what a token is for. */
|
|
4
|
+
export const TOKEN_PREFIX = { ingest: "wl_ik_", read: "wl_rk_", share: "wl_sh_" };
|
|
5
|
+
/** A new random token. Only its hash is stored. */
|
|
6
|
+
export const newToken = (kind) => `${TOKEN_PREFIX[kind]}${randomBytes(24).toString("base64url")}`;
|
|
7
|
+
export const hashToken = (token) => createHash("sha256").update(token).digest("hex");
|
|
8
|
+
export const newId = () => randomUUID();
|
|
9
|
+
/** A URL-safe short name from free text, for example "My Shop!" -> "my-shop". */
|
|
10
|
+
export const slugOf = (text) => text
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.normalize("NFKD")
|
|
13
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
14
|
+
.replace(/^-+|-+$/g, "")
|
|
15
|
+
.slice(0, 40) || "item";
|
|
16
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAElE,wGAAwG;AAExG,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAW,CAAC;AAG3F,mDAAmD;AACnD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAe,EAAU,EAAE,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAErH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAErG,MAAM,CAAC,MAAM,KAAK,GAAG,GAAW,EAAE,CAAC,UAAU,EAAE,CAAC;AAEhD,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAC7C,IAAI;KACD,WAAW,EAAE;KACb,SAAS,CAAC,MAAM,CAAC;KACjB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;KAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;KACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC"}
|