@bkazemi/verily 0.6.0 → 0.8.0
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/README.md +197 -91
- package/dist/browser/connect-dialog.js +3 -4
- package/dist/browser/evidence-dialog.d.ts +7 -2
- package/dist/browser/evidence-dialog.js +62 -26
- package/dist/browser/index.js +3 -0
- package/dist/browser/mark.d.ts +2 -6
- package/dist/browser/mark.js +6 -12
- package/dist/browser/provider-mark.d.ts +3 -3
- package/dist/browser/provider-mark.js +15 -90
- package/dist/core/index.d.ts +78 -2
- package/dist/core/index.js +26 -2
- package/dist/instance/auth.d.ts +14 -0
- package/dist/instance/auth.js +80 -0
- package/dist/instance/bin.d.ts +2 -0
- package/dist/instance/bin.js +36 -0
- package/dist/instance/cloudflare.d.ts +57 -0
- package/dist/instance/cloudflare.js +71 -0
- package/dist/instance/env.d.ts +51 -0
- package/dist/instance/env.js +84 -0
- package/dist/instance/index.d.ts +136 -0
- package/dist/instance/index.js +777 -0
- package/dist/instance/init.d.ts +6 -0
- package/dist/instance/init.js +28 -0
- package/dist/instance/limits.d.ts +30 -0
- package/dist/instance/limits.js +45 -0
- package/dist/instance/node.d.ts +13 -0
- package/dist/instance/node.js +43 -0
- package/dist/instance/page.d.ts +18 -0
- package/dist/instance/page.js +30 -0
- package/dist/instance/postgres.d.ts +19 -0
- package/dist/instance/postgres.js +83 -0
- package/dist/instance/routes.d.ts +7 -0
- package/dist/instance/routes.js +22 -0
- package/dist/instance/serve.d.ts +31 -0
- package/dist/instance/serve.js +49 -0
- package/dist/instance/sites.d.ts +169 -0
- package/dist/instance/sites.js +413 -0
- package/dist/instance/starter.d.ts +6 -0
- package/dist/instance/starter.js +99 -0
- package/dist/instance/store.d.ts +20 -0
- package/dist/instance/store.js +11 -0
- package/dist/instance/undici.d.ts +8 -0
- package/dist/instance/undici.js +9 -0
- package/dist/marks.d.ts +44 -0
- package/dist/marks.js +98 -0
- package/dist/server/discord.js +3 -0
- package/dist/server/domain.js +37 -10
- package/dist/server/github-gist.js +16 -4
- package/dist/server/github.js +3 -0
- package/dist/server/index.js +20 -47
- package/dist/server/link.js +9 -4
- package/dist/server/render.d.ts +49 -0
- package/dist/server/render.js +104 -0
- package/dist/server/service.d.ts +44 -1
- package/dist/server/service.js +225 -19
- package/dist/server/style.d.ts +1 -1
- package/dist/server/style.js +132 -2
- package/dist/server/youtube.js +5 -0
- package/dist/verily.js +155 -124
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +30 -2
package/README.md
CHANGED
|
@@ -16,24 +16,35 @@
|
|
|
16
16
|
<img src="docs/images/badge.png" alt="A profile card with a Verily badge showing a GitHub account" width="480" />
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
Verily
|
|
19
|
+
Verily comes as a library you embed in your own backend, or as a ready-made server you run on Node or Cloudflare. A signed-in user of your site proves they control an external account, such as a GitHub account, a domain or an email address, by one of several [proof methods](#proof-methods). They approve the connection and choose who can read it. A public one is a record anyone can inspect, with a badge for their profile. An unlisted one is kept off public pages.
|
|
20
20
|
|
|
21
21
|
It doesn't sign anyone into your site, and it doesn't establish legal identity.
|
|
22
22
|
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
- **Start here:** [How it works](#how-it-works) · [Ways to use it](#ways-to-use-it)
|
|
26
|
+
- **Embedding the backend:** [Install](#install) · [Embed the backend in your app](#embed-the-backend-in-your-app) · [Show the badge](#show-the-badge) · [Proof methods](#proof-methods) · [Operations](#operations)
|
|
27
|
+
- **Running an instance:** [Run an instance](#run-an-instance) · [A badge on a static site](#a-badge-on-a-static-site)
|
|
28
|
+
- **Using someone else's instance:** [Using a hosted instance](#using-a-hosted-instance)
|
|
29
|
+
- **Reference:** [Serving other sites](#serving-other-sites) · [Signed records](#signed-records) · [What DNS says of the verifier](#what-dns-says-of-the-verifier) · [Development](#development) · [License](#license)
|
|
30
|
+
|
|
23
31
|
## How it works
|
|
24
32
|
|
|
25
33
|
1. Your site tells Verily who is signed in. Verily never handles your logins.
|
|
26
|
-
2. The user opens
|
|
27
|
-
3. They approve the connection. Verily stores it and publishes the evidence at a public URL.
|
|
34
|
+
2. The user opens Verily's connect dialog on your site, picks a method, and proves they control the external account.
|
|
35
|
+
3. They approve the connection and choose who can read it. Verily stores it, and publishes the evidence of a public one at a public URL.
|
|
28
36
|
4. A `<verily-badge>` on their profile shows the external account. Clicking it opens the evidence.
|
|
29
37
|
5. Verily keeps the record honest: published proofs are re-read, records expire, and either side can revoke.
|
|
30
38
|
|
|
31
|
-
##
|
|
39
|
+
## Ways to use it
|
|
40
|
+
|
|
41
|
+
Verily's backend comes in two forms, and a site can also use one that someone else runs.
|
|
32
42
|
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
43
|
+
- **Embed it in your app.** `createVerily` runs inside your backend and links your app's own accounts. Your app says who is signed in and registers with each sign-in provider. See [Embed the backend in your app](#embed-the-backend-in-your-app).
|
|
44
|
+
- **Run an instance.** `createInstance` is a complete app with one owner, who signs in with a key. Other sites can send their users to it. A running one is called an instance. See [Run an instance](#run-an-instance).
|
|
45
|
+
- **Use an instance someone else runs.** It holds the provider apps and the records. Your backend makes two calls. See [Using a hosted instance](#using-a-hosted-instance).
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
All three give your backend the same records, so the code that shows them stays the same if you switch.
|
|
37
48
|
|
|
38
49
|
## Install
|
|
39
50
|
|
|
@@ -41,19 +52,24 @@ Both give your backend the same records in the same shape, so the code that show
|
|
|
41
52
|
npm install @bkazemi/verily
|
|
42
53
|
```
|
|
43
54
|
|
|
44
|
-
Requires Node.js 22.13+ or 24+. In Node, `@bkazemi/verily` exports the server. Browser bundlers get a browser-only entry with no server or database code. With TypeScript, use `moduleResolution: "NodeNext"` for the server and `"Bundler"` for the browser.
|
|
55
|
+
Requires Node.js 22.13+ or 24+. In Node, `@bkazemi/verily` exports the server. Browser bundlers get a browser-only entry with no server or database code. `@bkazemi/verily/instance` is the standalone backend, and `@bkazemi/verily/site` is the client for a site that uses someone else's instance. With TypeScript, use `moduleResolution: "NodeNext"` for the server and `"Bundler"` for the browser.
|
|
45
56
|
|
|
46
|
-
##
|
|
57
|
+
## Embed the backend in your app
|
|
47
58
|
|
|
48
59
|
### 1. Storage
|
|
49
60
|
|
|
50
61
|
```ts
|
|
51
62
|
import { Pool, PostgresStorage } from '@bkazemi/verily';
|
|
52
63
|
|
|
53
|
-
const storage = new PostgresStorage(
|
|
54
|
-
|
|
64
|
+
const storage = new PostgresStorage(
|
|
65
|
+
new Pool({ connectionString: process.env.DATABASE_URL }),
|
|
66
|
+
);
|
|
67
|
+
// creates its one table if it doesn't exist
|
|
68
|
+
await storage.migrate();
|
|
55
69
|
```
|
|
56
70
|
|
|
71
|
+
Postgres ships with the package. Anything implementing the `Storage` interface in [`src/core/index.ts`](src/core/index.ts) works too.
|
|
72
|
+
|
|
57
73
|
### 2. Create the handler
|
|
58
74
|
|
|
59
75
|
```ts
|
|
@@ -73,14 +89,18 @@ const verily = createVerily({
|
|
|
73
89
|
profileOrigins: ['https://community.example'],
|
|
74
90
|
reportUrl: 'mailto:reports@community.example',
|
|
75
91
|
authenticate: async (request) => {
|
|
76
|
-
|
|
92
|
+
// your own session lookup
|
|
93
|
+
const user = await getSignedInUser(request);
|
|
77
94
|
if (!user) return undefined;
|
|
78
95
|
|
|
79
96
|
return {
|
|
80
|
-
|
|
97
|
+
// private, stable, never reused
|
|
98
|
+
id: user.id,
|
|
81
99
|
label: user.displayName,
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
// public and durable; never an email
|
|
101
|
+
reference: user.username,
|
|
102
|
+
// optional
|
|
103
|
+
profileUrl: `https://community.example/users/${user.username}`,
|
|
84
104
|
};
|
|
85
105
|
},
|
|
86
106
|
});
|
|
@@ -96,11 +116,11 @@ const verily = createVerily({
|
|
|
96
116
|
| `reportUrl` | Where readers report a bad record (`https:` or `mailto:`). |
|
|
97
117
|
| `authenticate` | Returns the signed-in local account for a request, or `undefined` if nobody is signed in. |
|
|
98
118
|
|
|
99
|
-
Sign-in methods need an app registered with the provider
|
|
119
|
+
Sign-in methods need an app registered with the provider. The callback URL is always `<baseUrl>/callback`, and every place you run Verily, development included, needs its own registered.
|
|
100
120
|
|
|
101
121
|
- **GitHub:** [register an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) and set its callback URL. An OAuth app has only one, so development and production each need their own app.
|
|
102
122
|
- **Discord:** create an application in the [Discord developer portal](https://discord.com/developers/applications) and add the callback URL under **OAuth2 → Redirects**. One application can list several. Discord shows the client secret only when you reset it.
|
|
103
|
-
- **YouTube:** in the [Google Cloud console](https://console.cloud.google.com/), enable the YouTube Data API v3, create an OAuth client of type **Web application**, and add the callback URL as an authorized redirect URI. One client can list several. The `youtube.readonly` scope is sensitive: until Google verifies your app, users see
|
|
123
|
+
- **YouTube:** in the [Google Cloud console](https://console.cloud.google.com/), enable the YouTube Data API v3, create an OAuth client of type **Web application**, and add the callback URL as an authorized redirect URI. One client can list several. The `youtube.readonly` scope is sensitive: until Google verifies your app, users see a warning and only 100 accounts can ever use it, so apply for verification before offering it publicly.
|
|
104
124
|
|
|
105
125
|
Pass the app's client id and secret to `githubProvider()`, `discordProvider()` or `youtubeProvider()`, and keep them on the server. The other methods need no registration.
|
|
106
126
|
|
|
@@ -129,10 +149,12 @@ Put the connect pill on your settings page. Clicking it opens a dialog where the
|
|
|
129
149
|
<verily-connect backend-url="/api/verily"></verily-connect>
|
|
130
150
|
```
|
|
131
151
|
|
|
132
|
-
|
|
152
|
+
A provider's sign-in opens in a small window, and the dialog carries on when the user returns. When a connection is recorded, the element fires `verily-result`, whose `detail` holds the new `connectionId`. The backend must be on the page's origin.
|
|
133
153
|
|
|
134
154
|
The full-page flow at `<baseUrl>/verify` still works, for links and for browsers without JavaScript. `<baseUrl>/mine` returns the user's connections as JSON, for your settings page.
|
|
135
155
|
|
|
156
|
+
**Public or unlisted.** At approval the user chooses who can read the connection; unlisted is the default. A public connection has an evidence page anyone can open, which is what a badge with a `connection-id` reads. An unlisted one has none. Its holder can make one sharing link at a time, good for 7 days (`shareTtlMs`), with `client.issueShare(id)`, and end it with `client.revokeShare(id)`. The user changes visibility at `<baseUrl>/visibility/<id>`, and `service.mine(account)` returns a user's own records, unlisted included.
|
|
157
|
+
|
|
136
158
|
Then schedule the upkeep described under [Operations](#operations).
|
|
137
159
|
|
|
138
160
|
## Show the badge
|
|
@@ -140,37 +162,44 @@ Then schedule the upkeep described under [Operations](#operations).
|
|
|
140
162
|
With a bundler:
|
|
141
163
|
|
|
142
164
|
```js
|
|
143
|
-
|
|
165
|
+
// also registers <verily-badge>
|
|
166
|
+
import { init } from '@bkazemi/verily';
|
|
144
167
|
|
|
145
168
|
const client = init({ backendUrl: '/api/verily' });
|
|
146
169
|
```
|
|
147
170
|
|
|
148
|
-
Without one, serve `node_modules/@bkazemi/verily/dist/verily.js` yourself, or load it from a CDN such as `https://cdn.jsdelivr.net/npm/@bkazemi/verily@0.
|
|
171
|
+
Without one, serve `node_modules/@bkazemi/verily/dist/verily.js` yourself, or load it from a CDN such as `https://cdn.jsdelivr.net/npm/@bkazemi/verily@0.8.0/dist/verily.js`. The script defines a global `Verily`.
|
|
149
172
|
|
|
150
173
|
Then place the badge wherever the account appears:
|
|
151
174
|
|
|
152
175
|
```html
|
|
153
176
|
<script src="/assets/verily.js" defer></script>
|
|
154
|
-
<verily-badge
|
|
177
|
+
<verily-badge
|
|
178
|
+
backend-url="/api/verily"
|
|
179
|
+
connection-id="CONNECTION_ID"
|
|
180
|
+
></verily-badge>
|
|
155
181
|
```
|
|
156
182
|
|
|
157
183
|
Clicking the badge opens the verification details:
|
|
158
184
|
|
|
159
185
|
<p align="center">
|
|
160
|
-
<img src="docs/images/dialog.png" alt="The verification details dialog: the local account, the linked GitHub account, its status, and when it was approved
|
|
186
|
+
<img src="docs/images/dialog.png" alt="The verification details dialog: the local account, the linked GitHub account and when it joined, its status, how it was shown, and when it was approved and expires" width="448" />
|
|
161
187
|
</p>
|
|
162
188
|
|
|
163
189
|
A user with several accounts gets one badge for all of them. Give it every connection id, in any order:
|
|
164
190
|
|
|
165
191
|
```html
|
|
166
|
-
<verily-badge
|
|
192
|
+
<verily-badge
|
|
193
|
+
backend-url="/api/verily"
|
|
194
|
+
connection-ids="ID_ONE ID_TWO ID_THREE"
|
|
195
|
+
></verily-badge>
|
|
167
196
|
```
|
|
168
197
|
|
|
169
|
-
It shows the account
|
|
198
|
+
It shows the first account connected, then a count of the rest, such as `+2`. Clicking it opens the details with one card per account. Only accounts verified now are counted, and an account on more than one record is shown once. The ids must all belong to the same user.
|
|
170
199
|
|
|
171
|
-
A holder can change
|
|
200
|
+
A holder can change the order. An account marked preferred leads the badge, and ones marked unused come last but still count. A retired account is kept as history: never counted, grouped at the foot of the details, and shown with the date it was last verified. A badge with only retired accounts is a quiet pill; to show nothing, leave retired records out.
|
|
172
201
|
|
|
173
|
-
On a desktop,
|
|
202
|
+
On a desktop, hovering or focusing the badge opens a small panel naming up to eight of the accounts. It needs the Popover API. Add `peek="off"` to leave it out.
|
|
174
203
|
|
|
175
204
|
Add `stacked` to name the accounts in the badge itself, one to a row:
|
|
176
205
|
|
|
@@ -182,21 +211,27 @@ Add `stacked` to name the accounts in the badge itself, one to a row:
|
|
|
182
211
|
></verily-badge>
|
|
183
212
|
```
|
|
184
213
|
|
|
185
|
-
|
|
214
|
+
<img src="docs/images/stacked.png" alt="A stacked badge listing four accounts, one to a row, then +2 more" width="213" />
|
|
215
|
+
|
|
216
|
+
It lists up to four verified accounts, then counts the rest, such as `+2 more`. It is as tall as its rows, so give it a line of its own. `mountBadges` and `presentConnections` take the same choices as `{ stacked: true }` and `{ peek: false }`.
|
|
186
217
|
|
|
187
|
-
|
|
218
|
+
With several accounts, each card shows the name, status and verifier, with the method and dates behind a Details toggle. Add `details-open`, or pass `{ detailsOpen: true }`, to start every card open.
|
|
188
219
|
|
|
189
|
-
The badge refreshes every 30 seconds
|
|
220
|
+
The badge refreshes every 30 seconds and reads only public evidence. The client can also start and end connections, and draw a badge into your own element:
|
|
190
221
|
|
|
191
222
|
```js
|
|
192
|
-
|
|
193
|
-
await client.
|
|
194
|
-
|
|
223
|
+
// the connect dialog; call from a click
|
|
224
|
+
await client.openConnect();
|
|
225
|
+
// opens <baseUrl>/verify in a popup; call from a click
|
|
226
|
+
await client.connect({ provider: 'github' });
|
|
227
|
+
// draws once; call again to refresh
|
|
228
|
+
await client.mountBadge(element, { connectionId });
|
|
195
229
|
await client.disconnect(connectionId);
|
|
196
|
-
|
|
230
|
+
// or 'unused', 'current', 'retired'
|
|
231
|
+
await client.mark(connectionId, 'preferred');
|
|
197
232
|
```
|
|
198
233
|
|
|
199
|
-
`mark`
|
|
234
|
+
`mark` acts on the account, across every record of it the user has. Preferring one account takes the mark from another. Retiring freezes its records: they are never rechecked or read as verified, cannot change visibility, and are kept until removed. Only a renewal by a record's first method brings it back.
|
|
200
235
|
|
|
201
236
|
To match your site's look, set any of these CSS variables on an ancestor: `--verily-surface`, `--verily-text`, `--verily-border`, `--verily-muted`, `--verily-hover`, `--verily-font-family`, `--verily-font-size`.
|
|
202
237
|
|
|
@@ -213,6 +248,7 @@ import {
|
|
|
213
248
|
dnsProvider,
|
|
214
249
|
wellKnownProvider,
|
|
215
250
|
pgpProvider,
|
|
251
|
+
emailProvider,
|
|
216
252
|
} from '@bkazemi/verily';
|
|
217
253
|
```
|
|
218
254
|
|
|
@@ -229,7 +265,9 @@ import {
|
|
|
229
265
|
| `pgpProvider()` | signing a line Verily gives them with their OpenPGP key, then pasting it and the key. | None. |
|
|
230
266
|
| `emailProvider()` | pressing a button, or entering a code, that Verily mails to their address. | A function that sends one message. |
|
|
231
267
|
|
|
232
|
-
|
|
268
|
+
Apart from the sign-ins and the mailed code, the user publishes the proof in their own time and comes back. A proof that fails can be fixed and resubmitted up to five times. Gists, link-backs, DNS records and files can be taken down later, so Verily re-reads them on a schedule. A PGP signature is kept by Verily and published at `<baseUrl>/connections/<id>/proof`.
|
|
269
|
+
|
|
270
|
+
**How old an account is.** Signing in with GitHub, Discord or YouTube also reads when the account was made. It is kept on the record as `external.createdAt`, in milliseconds, shown as "Joined" beside the account's name, and covered by a signed record. The other methods learn no date, so a record first shown by one of them has none until its holder also signs in. A provider of your own sets it by returning `createdAt` from `authenticate()`.
|
|
233
271
|
|
|
234
272
|
**Several at once.** List more than one, and `/verify` offers each method as its own button:
|
|
235
273
|
|
|
@@ -239,21 +277,21 @@ providers: [githubProvider({ clientId, clientSecret }), githubLinkProvider()],
|
|
|
239
277
|
|
|
240
278
|
Proving the same account a second way adds that proof to the existing record instead of creating another.
|
|
241
279
|
|
|
242
|
-
**Approval only where there is something to decide.** A new connection
|
|
280
|
+
**Approval only where there is something to decide.** A new connection ends with the user choosing who can read it and confirming. A renewal, or a second proof of an account already linked, is recorded as soon as the proof checks out. That holds for flows started from your own pages. A flow entered through a link to `GET <baseUrl>/sessions` still ends on an approval, because any site can send a browser down a link.
|
|
243
281
|
|
|
244
|
-
|
|
282
|
+
If you call `service.start()` yourself, pass `true` as its last argument only when the request provably came from the user's own page, as a same-origin POST does. Without it the flow always ends on an approval.
|
|
245
283
|
|
|
246
|
-
**
|
|
284
|
+
**A link-back found at sign-in.** With `githubProvider()` and `githubLinkProvider()` both listed, signing in with GitHub also reads the profile that signed in. If its website field already links to the user's `profileUrl`, the approval step says so, and confirming records the link-back beside the sign-in. A link with `http://` in place of `https://` counts; a different host, port or path does not.
|
|
247
285
|
|
|
248
|
-
**
|
|
286
|
+
**Link-backs.** GitHub and Mastodon already mark profile links `rel="me"`. By default the page may be on any public host, and the account is named by the page's address. That needs Node, because Verily checks each connection so the address cannot point inside your network. Pass `hosts` to read only certain hosts (required on Cloudflare Workers), and `profile` to name the account by handle. Your own `fetch` replaces that network check, so it must enforce the same rule. Only `<a>` and `<link>` elements in HTML, or a `Link:` header, count; [`src/server/link.ts`](src/server/link.ts) has the exact rules.
|
|
249
287
|
|
|
250
|
-
**
|
|
288
|
+
**Domains.** `dnsProvider()` proves a domain, including one that serves no page. The user adds a TXT record named `_verily` whose value is their `profileUrl`, and enters the domain. Verily looks it up through `https://dns.google/resolve`, so it runs on Cloudflare Workers, and the evidence links to that lookup. `wellKnownProvider()` reads `https://<domain>/.well-known/verily.txt` instead. It fetches a host the user names, so it follows the link-back's rules: Node, or `hosts`. Both name the exact domain entered and say nothing about its subdomains. Where the resolver validated the answer with DNSSEC, the evidence says so. A local account with no `profileUrl` is given a random value, `verily-proof=…`, to publish instead, and a renewal asks for the same one. List both providers and a domain proved one way picks up the other if it is already there.
|
|
251
289
|
|
|
252
|
-
|
|
290
|
+
**Your own method.** A provider is an object, and [`src/core/index.ts`](src/core/index.ts) documents each shape. An artifact provider may also set `field` and `input` to name what it asks for, `resolve` to turn something short such as a username into the address to read, and `known` to say where to look for an account already on record. A sign-in provider's `authorizationUrl` is passed `account` on a renewal or visibility change, so it can ask for that account by name.
|
|
253
291
|
|
|
254
292
|
**OpenPGP.** The key's fingerprint is the identity. An email address is shown only when the key signed it **and** either keys.openpgp.org has confirmed it or the address's domain publishes the key in its [web key directory](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/). The key must be valid when the proof is checked (not expired or revoked), SHA-1 signatures are refused, and a signing subkey must be properly bound to its key.
|
|
255
293
|
|
|
256
|
-
**Email.** Verily sends no mail itself. Give `emailProvider()` a `send` function and it hands
|
|
294
|
+
**Email.** Verily sends no mail itself. Give `emailProvider()` a `send` function and it hands it one message per flow, as HTML and plain text:
|
|
257
295
|
|
|
258
296
|
```ts
|
|
259
297
|
emailProvider({
|
|
@@ -264,7 +302,8 @@ emailProvider({
|
|
|
264
302
|
subject,
|
|
265
303
|
text,
|
|
266
304
|
html,
|
|
267
|
-
// The logo travels in the message. Attach each image inline under
|
|
305
|
+
// The logo travels in the message. Attach each image inline under
|
|
306
|
+
// its content id.
|
|
268
307
|
attachments: images.map((image) => ({
|
|
269
308
|
filename: image.filename,
|
|
270
309
|
content: Buffer.from(image.content, 'base64'),
|
|
@@ -275,44 +314,76 @@ emailProvider({
|
|
|
275
314
|
}),
|
|
276
315
|
```
|
|
277
316
|
|
|
278
|
-
The HTML loads nothing
|
|
317
|
+
The HTML loads nothing remote. Its one image, the Verily logo, is attached; a sender that drops `images` still sends a complete message.
|
|
279
318
|
|
|
280
|
-
The user types an address and Verily mails it a button
|
|
319
|
+
The user types an address and Verily mails it a button, with an eight-character code beneath as the other way in. In the browser that started the flow, opening the link proves the mailbox. In any other browser the page asks for one press of **Confirm**, so a mail scanner that follows links confirms nothing. The link and the code last the flow's ten minutes, and the code gets five tries. The address is the account's name on the record, so a public link shows it to everyone; the user is told before the code is sent. Like a sign-in, it proves the mailbox could be read that day, and nothing is re-read later.
|
|
281
320
|
|
|
282
|
-
A visitor chooses where the message goes, so Verily
|
|
321
|
+
A visitor chooses where the message goes, so Verily sends at most 100 messages in twenty-four hours and 5 to any one address. Set `sendLimits: { day, address }` to change them, or `Infinity` to lift one. The day's count is shared, so also rate limit `POST <baseUrl>/sessions` and `POST <baseUrl>/flows/*/submit`.
|
|
283
322
|
|
|
284
323
|
`resendSender({ apiKey, from })` is a ready-made `send` for [Resend](https://resend.com), using only `fetch`:
|
|
285
324
|
|
|
286
325
|
```ts
|
|
287
326
|
emailProvider({
|
|
288
|
-
send: resendSender({
|
|
327
|
+
send: resendSender({
|
|
328
|
+
apiKey: process.env.RESEND_API_KEY!,
|
|
329
|
+
from: 'Verily <verify@community.example>',
|
|
330
|
+
}),
|
|
289
331
|
}),
|
|
290
332
|
```
|
|
291
333
|
|
|
292
334
|
**Matching accounts.** Two methods agree on an account when the provider's account ids match. A link-back only knows an address, so it's matched by profile URL instead, and only in flows the user starts themselves. Removing a connection from the external side always needs a matching provider account id.
|
|
293
335
|
|
|
336
|
+
## Run an instance
|
|
337
|
+
|
|
338
|
+
An instance runs on Cloudflare or on Node from the same settings. `PUBLIC_ORIGIN`, `SITE_NAME`, `OWNER_LABEL`, `OWNER_REFERENCE`, `OWNER_PROFILE_URL`, `REPORT_URL` and `OWNER_KEY` are required. `OWNER_KEY` is the secret you sign in with: at least 32 random bytes, base64url.
|
|
339
|
+
|
|
340
|
+
```sh
|
|
341
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
A link back from a GitHub profile, a DNS record and an OpenPGP signature are always offered. GitHub, Discord and YouTube sign-in and email are each offered once their secrets are set. Each sign-in app needs `<PUBLIC_ORIGIN>/callback` as a redirect. [`src/instance/env.ts`](src/instance/env.ts) lists every setting.
|
|
345
|
+
|
|
346
|
+
**On Cloudflare.**
|
|
347
|
+
|
|
348
|
+
```sh
|
|
349
|
+
npx @bkazemi/verily init cloudflare my-instance
|
|
350
|
+
cd my-instance && npm install
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Fill in `wrangler.jsonc`, run `npx wrangler secret put OWNER_KEY`, then `npm run deploy`. To take a new release, update the dependency and deploy again.
|
|
354
|
+
|
|
355
|
+
**On Node.** Install the package, set the settings and `DATABASE_URL` in the environment, and run `npx verily serve`. It keeps everything in one Postgres table, which it creates, and listens on `127.0.0.1:3000` for a proxy that terminates TLS. `HOST` and `PORT` change where. Set `CLIENT_ADDRESS_HEADER` to the header your proxy sets to the client's address, such as `X-Real-IP`. Without it every visitor is counted as the proxy and shares one rate limit.
|
|
356
|
+
|
|
357
|
+
**In your own server.** `createInstance` from `@bkazemi/verily/instance` takes the same options and a store, and returns a request handler and an `upkeep()` to run hourly.
|
|
358
|
+
|
|
359
|
+
Cloudflare and `verily serve` both run the hourly upkeep themselves.
|
|
360
|
+
|
|
361
|
+
**Serving other sites.** List each site in `SITES`, as JSON, with `id`, `name`, `origin`, `authorizeUrl` and `returnUrl`, and put its key in `SITE_<ID>_KEY`. Give the site its id and key, and it uses the client under [Using a hosted instance](#using-a-hosted-instance). A site may also carry `providers` and `visibility`, to narrow what its users are offered.
|
|
362
|
+
|
|
294
363
|
## A badge on a static site
|
|
295
364
|
|
|
296
|
-
A static site only needs the script and the badge markup; the backend runs somewhere else over HTTPS. The quickest backend is
|
|
365
|
+
A static site only needs the script and the badge markup; the backend runs somewhere else over HTTPS. The quickest backend is an instance of your own on Cloudflare:
|
|
297
366
|
|
|
298
|
-
1.
|
|
299
|
-
2.
|
|
300
|
-
3.
|
|
301
|
-
4. Add the script and the badge to your site:
|
|
367
|
+
1. Set one up and deploy it, as [Run an instance](#run-an-instance) describes.
|
|
368
|
+
2. On the instance, sign in with `OWNER_KEY`, verify, and approve a **public** connection. Its id is on your settings page there, in the markup to embed.
|
|
369
|
+
3. Add the script and the badge to your site:
|
|
302
370
|
|
|
303
371
|
```html
|
|
304
|
-
<script
|
|
372
|
+
<script
|
|
373
|
+
src="https://cdn.jsdelivr.net/npm/@bkazemi/verily@0.8.0/dist/verily.js"
|
|
374
|
+
defer
|
|
375
|
+
></script>
|
|
305
376
|
<verily-badge
|
|
306
|
-
backend-url="https://your-
|
|
377
|
+
backend-url="https://verily.your-site.example"
|
|
307
378
|
connection-id="CONNECTION_ID"
|
|
308
379
|
></verily-badge>
|
|
309
380
|
```
|
|
310
381
|
|
|
311
|
-
Run the
|
|
382
|
+
Run the instance on a domain readers can connect to your site, because `PUBLIC_ORIGIN` is shown as the verifier. If your site sets a Content Security Policy, allow the instance in `connect-src`.
|
|
312
383
|
|
|
313
384
|
## Using a hosted instance
|
|
314
385
|
|
|
315
|
-
|
|
386
|
+
The instance's operator registers your site and gives you its id and a key. You register with no sign-in provider, store no records and run no backend. Your backend sends a signed-in user to the instance and reads their connections back.
|
|
316
387
|
|
|
317
388
|
You are trusting that operator. They can read every record made through their instance, unlisted ones included, and for sign-in and email methods a reader has only the operator's word that the account was verified. Link-back, gist and PGP proofs can be rechecked by anyone. A site that does not want to rely on an operator runs its own instance.
|
|
318
389
|
|
|
@@ -337,13 +408,17 @@ const handle = verily.handler({
|
|
|
337
408
|
if (!user) return undefined;
|
|
338
409
|
|
|
339
410
|
return {
|
|
340
|
-
|
|
411
|
+
// private and stable; the instance never shows it
|
|
412
|
+
id: user.id,
|
|
341
413
|
label: user.displayName,
|
|
342
|
-
|
|
414
|
+
// durable and safe to show; never an email
|
|
415
|
+
reference: user.handle,
|
|
343
416
|
};
|
|
344
417
|
},
|
|
345
|
-
|
|
346
|
-
|
|
418
|
+
// where a signed-out user is sent
|
|
419
|
+
signInUrl: '/login',
|
|
420
|
+
// where a user lands when they come back
|
|
421
|
+
returnUrl: '/settings',
|
|
347
422
|
});
|
|
348
423
|
|
|
349
424
|
// GET and POST /api/verily/*
|
|
@@ -374,7 +449,7 @@ Once a user has a link, show their badge in place of the pill, and give it the s
|
|
|
374
449
|
></verily-badge>
|
|
375
450
|
```
|
|
376
451
|
|
|
377
|
-
The badge's dialog then has Renew and Remove under each account, the controls that mark
|
|
452
|
+
The badge's dialog then has Renew and Remove under each account, the controls that mark or retire it, and Add account. Renew extends the record under the same id, so embeds keep working. It fires `verily-result` on each change. `verily.beginUrl('manage')` opens a user's links on the instance, for a site that would rather link there.
|
|
378
453
|
|
|
379
454
|
**3. Read connections where you show them.**
|
|
380
455
|
|
|
@@ -383,7 +458,7 @@ const { [user.id]: connections } = await verily.connections([user.id]);
|
|
|
383
458
|
const verified = connections.filter((c) => c.status === 'verified');
|
|
384
459
|
```
|
|
385
460
|
|
|
386
|
-
Show only `verified` records as verified. A `retired` record
|
|
461
|
+
Show only `verified` records as verified. A `retired` record carries `retiredAt` and can be shown as history, never as a current account. `mark` is `preferred` or `unused` where the user set one; it is their ordering, not something checked.
|
|
387
462
|
|
|
388
463
|
To show them, hand the records to the badge. It draws them as given and fetches nothing, so it works for unlisted links, which a browser cannot read for itself:
|
|
389
464
|
|
|
@@ -393,23 +468,28 @@ To show them, hand the records to the badge. It draws them as given and fetches
|
|
|
393
468
|
|
|
394
469
|
From script, set the property instead: `badge.connections = records`. Send a page only the records its reader may see: whatever is in the page, the reader has. Several records become one badge, as under [Show the badge](#show-the-badge), and an unlisted one opens its details without linking anywhere.
|
|
395
470
|
|
|
396
|
-
|
|
471
|
+
Unlisted records are included, and only your site can read them. `connections()` takes any number of ids, so read a whole page of users in one call. Every call reads the instance, so a removed or expired link stops showing on the next page load. `cacheMs` reuses an answer for that long, during which a revoked connection still shows as verified, so leave it unset or keep it to a few seconds. `{ fresh: true }` makes one call read anyway.
|
|
397
472
|
|
|
398
473
|
If you would rather your user ids never left your site, give the client a secret:
|
|
399
474
|
|
|
400
475
|
```ts
|
|
401
|
-
const verily = createSiteClient({
|
|
476
|
+
const verily = createSiteClient({
|
|
477
|
+
instance,
|
|
478
|
+
site,
|
|
479
|
+
key,
|
|
480
|
+
idSecret: process.env.VERILY_ID_SECRET,
|
|
481
|
+
});
|
|
402
482
|
```
|
|
403
483
|
|
|
404
|
-
|
|
484
|
+
The instance is then sent a stand-in for each id, and for a subject with no `reference`. The same secret and user always give the same stand-in, so nothing is stored. The secret must never change, so keep one for this alone.
|
|
405
485
|
|
|
406
486
|
A site that would rather write its own endpoints can: `verily.authorize(state, subject)` signs the redirect handoff, `verily.handoff(subject)` the pill's, and `verily.result(token)` checks what a user returns with.
|
|
407
487
|
|
|
408
|
-
**Moving
|
|
488
|
+
**Moving off someone else's instance.** To [run an instance](#run-an-instance) of your own, register your site with it and change the client's `instance`, `site` and `key`; the code above stays. To embed the backend, replace `verily.connections([id])` with `service.mine(account)`, which returns the same records, and the three steps above with the handler from [Embed the backend in your app](#embed-the-backend-in-your-app). Either way records are not moved, so users verify again.
|
|
409
489
|
|
|
410
490
|
## Serving other sites
|
|
411
491
|
|
|
412
|
-
One backend can serve sites that don't run Verily themselves, so none of them registers anything with a sign-in provider. A subject from such a site names it in `LocalAccount.siteName`, and every page, proof line and record describing that subject uses the name in place of the `siteName` option. The backend stays the verifier.
|
|
492
|
+
One backend can serve sites that don't run Verily themselves, so none of them registers anything with a sign-in provider. An instance does this already, from its `SITES` setting. This section is for an embedded backend that does it itself. A subject from such a site names it in `LocalAccount.siteName`, and every page, proof line and record describing that subject uses the name in place of the `siteName` option. The backend stays the verifier.
|
|
413
493
|
|
|
414
494
|
Three options tie a flow to the site that sent the holder:
|
|
415
495
|
|
|
@@ -421,17 +501,17 @@ Three options tie a flow to the site that sent the holder:
|
|
|
421
501
|
|
|
422
502
|
Two more narrow what a subject is offered, for a backend whose sites don't all want the same. `providersFor(local)` returns the provider ids a subject may use: it is offered those alone, in that order, and a flow for any other is refused. `visibilityFor(local)` returns the visibilities it may choose: with one, the approval page states it and offers no choice.
|
|
423
503
|
|
|
424
|
-
`result` is recorded when the flow ends and never changes: its kind, how it ended, the connection and its visibility at that moment, and `finishedAt`.
|
|
504
|
+
`result` is recorded when the flow ends and never changes: its kind, how it ended, the connection and its visibility at that moment, and `finishedAt`. An instance is built on these hooks, and [`src/instance/sites.ts`](src/instance/sites.ts) is a worked example of them.
|
|
425
505
|
|
|
426
506
|
## Signed records
|
|
427
507
|
|
|
428
|
-
Evidence is normally believed because the verifier's own origin serves it. Set a signing key and each public record can also be downloaded as a file signed with OpenPGP, which anyone can check later with `gpg`, without the
|
|
508
|
+
Evidence is normally believed because the verifier's own origin serves it. Set a signing key and each public record can also be downloaded as a file signed with OpenPGP, which anyone can check later with `gpg`, without the verifier being up.
|
|
429
509
|
|
|
430
510
|
```ts
|
|
431
511
|
import { createVerily, generateSigningKey } from '@bkazemi/verily';
|
|
432
512
|
|
|
433
|
-
// Make the key once and keep it with your other secrets. It is an armored
|
|
434
|
-
// private key, so it spans several lines.
|
|
513
|
+
// Make the key once and keep it with your other secrets. It is an armored
|
|
514
|
+
// OpenPGP private key, so it spans several lines.
|
|
435
515
|
console.log(await generateSigningKey('verifier.example'));
|
|
436
516
|
|
|
437
517
|
createVerily({
|
|
@@ -443,9 +523,9 @@ createVerily({
|
|
|
443
523
|
With a key set:
|
|
444
524
|
|
|
445
525
|
- **`GET <baseUrl>/connections/<id>?format=signed`** returns the signed file for a public record that is currently verified: the record as JSON under an OpenPGP cleartext signature. It is signed when asked for, so it says the record stood at that moment. An unlisted, revoked or expired record has no signed form. A public retired record has one, signed as `version: 2` with `status: "retired"` and `retiredAt`, so a reader that knows only version 1 refuses it and never takes it for a record that stood.
|
|
446
|
-
- **`GET <baseUrl>/keys.asc`** is the public key as `gpg --import` reads it. **`GET <baseUrl>/keys`** lists the same keys as JSON, each with its fingerprint.
|
|
526
|
+
- **`GET <baseUrl>/keys.asc`** is the public key as `gpg --import` reads it. **`GET <baseUrl>/keys`** lists the same keys as JSON, each with its fingerprint. An instance also serves that list at `/.well-known/verily-keys.json`.
|
|
447
527
|
- **`<baseUrl>/check`** is a page where a saved file can be pasted and read back.
|
|
448
|
-
- The evidence page
|
|
528
|
+
- The evidence page links to the signed record. The badge's dialog checks the signature in the browser and shows a `signed` pill after the verifier. A record whose signature fails is shown as unconfirmed, with `bad signature` in the pill's place.
|
|
449
529
|
- The approval step tells the holder that a saved signed record outlives removal.
|
|
450
530
|
|
|
451
531
|
To check a file yourself:
|
|
@@ -460,15 +540,18 @@ Or in code, which runs in a browser too:
|
|
|
460
540
|
```ts
|
|
461
541
|
import { verifySigned } from '@bkazemi/verily';
|
|
462
542
|
|
|
463
|
-
const { keys } = await (
|
|
464
|
-
|
|
543
|
+
const { keys } = await (
|
|
544
|
+
await fetch('https://verifier.example/api/verily/keys')
|
|
545
|
+
).json();
|
|
546
|
+
// undefined if no key signed it
|
|
547
|
+
const record = await verifySigned(file, keys);
|
|
465
548
|
```
|
|
466
549
|
|
|
467
550
|
A signed record proves what the verifier claimed and when (`issuedAt`). It does not prove the claim was true, and it does not say whether the record still stands: ask `evidenceUrl` for that. Save the keys with the file if it has to outlive the verifier's domain. When you change keys, list the old key from `/keys` in `retiredKeys` so what it signed still checks.
|
|
468
551
|
|
|
469
552
|
`generateSigningKey()` makes an Ed25519 key in the OpenPGP form GnuPG reads, with no subkeys. Any OpenPGP key that signs with its primary key works as `signingKey`; one that is revoked, expired or passphrase-protected is refused. Checking is done by OpenPGP.js under the format's own rules: a key is judged as it stood when the record was signed, so an expired key, or one revoked as retired or superseded, still answers for records it signed before then, and a key revoked as compromised signs nothing at all.
|
|
470
553
|
|
|
471
|
-
|
|
554
|
+
An instance signs by default, with a key it makes and stores itself. Set `SIGNING_KEY` to supply your own, or `SIGNING` to `off` to sign nothing.
|
|
472
555
|
|
|
473
556
|
## What DNS says of the verifier
|
|
474
557
|
|
|
@@ -484,41 +567,48 @@ Two TXT records let a reader check the verifier against DNS instead of taking it
|
|
|
484
567
|
```ts
|
|
485
568
|
import { dnsKeys, signedBy, verifySigned } from '@bkazemi/verily';
|
|
486
569
|
|
|
487
|
-
|
|
570
|
+
// keys saved with the file, or from /keys
|
|
571
|
+
const record = await verifySigned(file, keys);
|
|
488
572
|
const named = await dnsKeys(new URL(record.evidenceUrl).hostname);
|
|
489
573
|
|
|
490
|
-
|
|
491
|
-
named.
|
|
574
|
+
// the verifier's domain names the key that signed it
|
|
575
|
+
named.ids.includes(await signedBy(file));
|
|
576
|
+
// whether the resolver validated that answer
|
|
577
|
+
named.dnssec;
|
|
492
578
|
```
|
|
493
579
|
|
|
494
580
|
**The site's verifier.** A record's local side is the verifier's statement of what the site told it. On the site's own pages that is enough, since the site put the badge there. On a shared evidence link or a saved signed record, nothing shows the site chose this verifier. A site whose verifier is on another domain publishes `verily-verifier=<host>` on the domain of its `profileUrl`, with the port if `baseUrl` has one. `dnsVerifiers(domain)` reads it back. A verifier on the site's own domain, or a subdomain of it, needs no record and none is looked for.
|
|
495
581
|
|
|
496
|
-
Set `dns: true` and the
|
|
582
|
+
Set `dns: true` and the backend reads both through `https://dns.google/resolve`, the resolver the domain method asks:
|
|
497
583
|
|
|
498
584
|
```ts
|
|
499
585
|
createVerily({
|
|
500
586
|
// ...
|
|
501
|
-
|
|
587
|
+
// or { fetch, timeoutMs }
|
|
588
|
+
dns: true,
|
|
502
589
|
});
|
|
503
590
|
```
|
|
504
591
|
|
|
505
592
|
- The evidence page and `<baseUrl>/check` say the signing key is named in the verifier's DNS, with a link to the lookup, once it is. The answer is kept for ten minutes.
|
|
506
593
|
- When a link is approved, the site's domain is looked up. If it names this verifier, the record's local side carries the lookup as `artifactUrl` and the record as `expect`, the evidence reads "Stated by … whose DNS names this verifier" and links to the lookup, and the signed record includes it. `recheck()` reads each site again every `recheckMs`, one lookup for all of a site's records. A record loses the proof when the site stops naming the verifier, or when the lookup has gone unread for `freshnessMs`; its status is unaffected either way.
|
|
507
594
|
|
|
508
|
-
|
|
595
|
+
An instance sets `dns: true`.
|
|
509
596
|
|
|
510
597
|
## Operations
|
|
511
598
|
|
|
512
599
|
Verification lasts 30 days, flows 10 minutes, and sharing links 7 days. Change them with `validityMs`, `flowTtlMs` and `shareTtlMs`. Evidence is never cached.
|
|
513
600
|
|
|
514
|
-
|
|
601
|
+
A user's DNS setup is saved for seven days. If propagation outlasts a flow's ten minutes, they choose DNS again and get the same TXT value and the last domain entered. Each return reads the proof afresh. The TXT record has to stay after verification, for the ongoing checks.
|
|
602
|
+
|
|
603
|
+
Run the upkeep on a schedule, for example hourly. An instance does this itself.
|
|
515
604
|
|
|
516
605
|
```ts
|
|
517
606
|
setInterval(
|
|
518
607
|
async () => {
|
|
519
608
|
try {
|
|
520
609
|
await verily.service.prune();
|
|
521
|
-
|
|
610
|
+
// only needed if you use a method other than signing in
|
|
611
|
+
await verily.service.recheck();
|
|
522
612
|
} catch (error) {
|
|
523
613
|
console.error('Verily upkeep failed', error);
|
|
524
614
|
}
|
|
@@ -527,10 +617,23 @@ setInterval(
|
|
|
527
617
|
);
|
|
528
618
|
```
|
|
529
619
|
|
|
530
|
-
- **`prune()`** deletes expired flows, and deletes expired or revoked records after 90 days. A retired record is kept until it is removed.
|
|
531
|
-
- **`recheck()`** re-reads gists, link-backs, DNS records and files that are due, up to 5 per run (its `budget` argument)
|
|
620
|
+
- **`prune()`** deletes expired flows and DNS drafts, and deletes expired or revoked records after 90 days. A retired record is kept until it is removed.
|
|
621
|
+
- **`recheck()`** re-reads gists, link-backs, DNS records and files that are due, up to 5 per provider per run (its `budget` argument), each every 24 hours (`recheckMs`). A read that finds the proof gone makes the connection unconfirmed at once. A read that fails, such as a timeout, changes nothing, and the proof stays current for 7 days after its last successful read (`freshnessMs`). Either way it is read again after a quarter of `recheckMs`, and the connection is verified again as soon as a read finds the proof. A provider of your own says a proof is gone by throwing `Absent` from `verify`. For PGP, it asks the keyserver whether the key has been revoked, and revokes the connection if so.
|
|
622
|
+
|
|
623
|
+
- **`onChange`** is an option, not a call. Set it to hear when a record's status, visibility, expiry or mark changes, so your site can update its own copy without polling:
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
createVerily({
|
|
627
|
+
// ...
|
|
628
|
+
onChange: async ({ evidence, local, revision }) => {
|
|
629
|
+
await db.saveConnectionIfNewer(local.id, evidence, revision);
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
`evidence` is the record now, `local` is your subject with its private id, `previous` is what was last delivered, and `revision` counts that record's deliveries. Ignore a change whose `revision` is not higher than the last you applied, since a slow call can finish after a newer one. A change made by a request is delivered before the request returns, so keep the call quick. Changes that come with time alone, such as expiry, are delivered by `recheck()`, or by `service.announce()` if you do not recheck. Delivery is at least once: a call that throws is retried on the next run, and turning the option on reports every existing record once, 50 per run. A record removed by `prune()` is not reported.
|
|
532
635
|
|
|
533
|
-
**If you never call `recheck()`, set `freshnessMs: Infinity`.** Otherwise connections proved by gist, link-back or PGP lapse after a week.
|
|
636
|
+
**If you never call `recheck()`, set `freshnessMs: Infinity`.** Otherwise connections proved by gist, link-back, DNS record, file or PGP lapse after a week.
|
|
534
637
|
|
|
535
638
|
## Development
|
|
536
639
|
|
|
@@ -541,7 +644,8 @@ The full example also needs Postgres and a GitHub OAuth app, a Discord applicati
|
|
|
541
644
|
```sh
|
|
542
645
|
npm run build
|
|
543
646
|
docker compose up -d --wait
|
|
544
|
-
|
|
647
|
+
# add credentials for at least one sign-in, and a long EXAMPLE_PASSWORD
|
|
648
|
+
cp example/.env.example .env
|
|
545
649
|
node --env-file=.env --import tsx example/server.ts
|
|
546
650
|
```
|
|
547
651
|
|
|
@@ -550,14 +654,16 @@ Checks and tests:
|
|
|
550
654
|
```sh
|
|
551
655
|
npm run build
|
|
552
656
|
npm run check
|
|
553
|
-
|
|
657
|
+
# npm run format fixes formatting
|
|
658
|
+
npm run format:check
|
|
554
659
|
npm test
|
|
555
660
|
TEST_DATABASE_URL=postgres://verily:verily@localhost:5432/verily npm test
|
|
556
661
|
npm run test:consumer
|
|
662
|
+
npm run starter:check
|
|
557
663
|
```
|
|
558
664
|
|
|
559
|
-
The Postgres and example tests only run when `TEST_DATABASE_URL` is set. The other tests use a fake provider and in-memory storage, so a live GitHub sign-in still has to be tried by hand. `test:consumer` installs the packed package into a separate project and checks its Node and browser imports. CI runs all of it, Postgres included, on Node 22.13, 24 and 26.
|
|
665
|
+
The Postgres and example tests only run when `TEST_DATABASE_URL` is set. The other tests use a fake provider and in-memory storage, so a live GitHub sign-in still has to be tried by hand. `test:consumer` installs the packed package into a separate project and checks its Node and browser imports. `starter:check` writes the project `verily init cloudflare` gives a new instance and bundles it as a deploy would, without deploying. CI runs all of it, Postgres included, on Node 22.13, 24 and 26.
|
|
560
666
|
|
|
561
667
|
## License
|
|
562
668
|
|
|
563
|
-
MIT. Signed records and `pgpProvider()` use [OpenPGP.js](https://github.com/openpgpjs/openpgpjs), which is LGPL-3.0-or-later. The badge script `verily.js` includes it, unmodified, to check signatures; its
|
|
669
|
+
MIT. Signed records and `pgpProvider()` use [OpenPGP.js](https://github.com/openpgpjs/openpgpjs), which is LGPL-3.0-or-later. The badge script `verily.js` includes it, unmodified, to check signatures; its license is in [`docs/licenses/openpgp.txt`](docs/licenses/openpgp.txt).
|