@bkazemi/verily 0.1.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/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/browser/badge.d.ts +39 -0
- package/dist/browser/badge.js +440 -0
- package/dist/browser/connect-dialog.d.ts +83 -0
- package/dist/browser/connect-dialog.js +514 -0
- package/dist/browser/evidence-dialog.d.ts +59 -0
- package/dist/browser/evidence-dialog.js +541 -0
- package/dist/browser/index.d.ts +59 -0
- package/dist/browser/index.js +809 -0
- package/dist/browser/logo.d.ts +2 -0
- package/dist/browser/logo.js +17 -0
- package/dist/browser/mark.d.ts +19 -0
- package/dist/browser/mark.js +77 -0
- package/dist/browser/provider-mark.d.ts +6 -0
- package/dist/browser/provider-mark.js +99 -0
- package/dist/browser/signed.d.ts +40 -0
- package/dist/browser/signed.js +219 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +3 -0
- package/dist/core/index.d.ts +454 -0
- package/dist/core/index.js +128 -0
- package/dist/core/signed.d.ts +49 -0
- package/dist/core/signed.js +113 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/logo.d.ts +21 -0
- package/dist/logo.js +38 -0
- package/dist/postgres/index.d.ts +12 -0
- package/dist/postgres/index.js +76 -0
- package/dist/server/addresses.d.ts +2 -0
- package/dist/server/addresses.js +61 -0
- package/dist/server/body.d.ts +11 -0
- package/dist/server/body.js +49 -0
- package/dist/server/copy.d.ts +8 -0
- package/dist/server/copy.js +42 -0
- package/dist/server/discord.d.ts +7 -0
- package/dist/server/discord.js +69 -0
- package/dist/server/email-logo.d.ts +11 -0
- package/dist/server/email-logo.js +122 -0
- package/dist/server/email.d.ts +46 -0
- package/dist/server/email.js +145 -0
- package/dist/server/escape.d.ts +2 -0
- package/dist/server/escape.js +2 -0
- package/dist/server/github-gist.d.ts +11 -0
- package/dist/server/github-gist.js +68 -0
- package/dist/server/github.d.ts +7 -0
- package/dist/server/github.js +65 -0
- package/dist/server/index.d.ts +70 -0
- package/dist/server/index.js +862 -0
- package/dist/server/link.d.ts +64 -0
- package/dist/server/link.js +458 -0
- package/dist/server/openpgp.d.ts +49 -0
- package/dist/server/openpgp.js +158 -0
- package/dist/server/pgp.d.ts +29 -0
- package/dist/server/pgp.js +198 -0
- package/dist/server/public-fetch.d.ts +23 -0
- package/dist/server/public-fetch.js +62 -0
- package/dist/server/resend.d.ts +13 -0
- package/dist/server/resend.js +37 -0
- package/dist/server/service.d.ts +311 -0
- package/dist/server/service.js +1178 -0
- package/dist/server/signing.d.ts +16 -0
- package/dist/server/signing.js +56 -0
- package/dist/server/style.d.ts +17 -0
- package/dist/server/style.js +244 -0
- package/dist/server/wkd.d.ts +7 -0
- package/dist/server/wkd.js +65 -0
- package/dist/server/youtube.d.ts +11 -0
- package/dist/server/youtube.js +99 -0
- package/dist/site/index.d.ts +139 -0
- package/dist/site/index.js +239 -0
- package/dist/verily.js +11907 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.js +10 -0
- package/docs/licenses/gpl-3.0.txt +232 -0
- package/docs/licenses/octicons.txt +21 -0
- package/docs/licenses/openpgp.txt +172 -0
- package/docs/licenses/rubik.txt +93 -0
- package/docs/licenses/simple-icons.txt +8 -0
- package/package.json +86 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
export type Visibility = 'public' | 'unlisted';
|
|
2
|
+
/**
|
|
3
|
+
* `unconfirmed` is a published proof that has not been read lately. It is not disproved,
|
|
4
|
+
* and turns back to `verified` the moment the proof reads again. `expired` is an approval
|
|
5
|
+
* that ran out, which only the holder renewing it undoes.
|
|
6
|
+
*/
|
|
7
|
+
export type Status = 'verified' | 'unconfirmed' | 'expired' | 'revoked';
|
|
8
|
+
/** What a site links: one of its accounts, a single page, or the site itself. */
|
|
9
|
+
export type LocalKind = 'account' | 'page' | 'site';
|
|
10
|
+
/** id is private and must never be reassigned. reference is durable and public-safe. */
|
|
11
|
+
export interface LocalAccount {
|
|
12
|
+
id: string;
|
|
13
|
+
/** Absent means the site did not say. Renderers must not assume an account. */
|
|
14
|
+
kind?: LocalKind;
|
|
15
|
+
label: string;
|
|
16
|
+
reference: string;
|
|
17
|
+
/** Canonical URL for the linked subject: a profile, a page, or the site root. */
|
|
18
|
+
profileUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The site this subject belongs to, where one instance serves several. Absent means the
|
|
21
|
+
* instance's own `siteName`. Every page and record describing the subject names this.
|
|
22
|
+
*/
|
|
23
|
+
siteName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ExternalAccount {
|
|
26
|
+
id: string;
|
|
27
|
+
/**
|
|
28
|
+
* What the other side is. Absent means an account, which is what a provider issues and
|
|
29
|
+
* can reassign. A key is not an account: nobody issued it and nobody can hand it over.
|
|
30
|
+
* A page is an address that was read and nothing more: a method that only fetches a
|
|
31
|
+
* document learns what it says, never whose account the address is. A mailbox is an
|
|
32
|
+
* account in all but how it is written: its domain issued it and can hand it to somebody
|
|
33
|
+
* else, but its address is already its whole name and it has no profile to link to.
|
|
34
|
+
*/
|
|
35
|
+
kind?: 'account' | 'key' | 'page' | 'mailbox';
|
|
36
|
+
handle: string;
|
|
37
|
+
/** Where a reader goes to see the subject. For a mailbox, its `mailto:` address. */
|
|
38
|
+
profileUrl: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* How control of one side was shown. A provider is who an account belongs to; a method is
|
|
42
|
+
* how control of it was demonstrated, and the two multiply rather than enumerate: one
|
|
43
|
+
* provider may support several methods and one method spans providers.
|
|
44
|
+
*
|
|
45
|
+
* `declared` is the site asserting a subject from its own records. That is not a weaker
|
|
46
|
+
* form of the others: a site is the only authority on its own namespace, so no external
|
|
47
|
+
* source could improve on it. A sign-in and a mailed code happen once, between the holder
|
|
48
|
+
* and this backend. The rest publish an artifact a reader can fetch.
|
|
49
|
+
*/
|
|
50
|
+
export type Method = 'declared' | 'oauth' | 'gist' | 'backlink' | 'signature' | 'code';
|
|
51
|
+
/** Who vouches for one side: this backend from its own records, or the account's provider. */
|
|
52
|
+
export type Attester = 'backend' | 'provider';
|
|
53
|
+
/**
|
|
54
|
+
* How one side of a link was established. `artifactUrl` is present only when the method
|
|
55
|
+
* leaves a public proof, which is what lets a reader check the claim without trusting
|
|
56
|
+
* this backend, and lets another installation reverify it independently.
|
|
57
|
+
*/
|
|
58
|
+
export interface Attestation {
|
|
59
|
+
by: Attester;
|
|
60
|
+
method: Method;
|
|
61
|
+
/** Public location of the proof. Never a sharing link or any other secret. */
|
|
62
|
+
artifactUrl?: string;
|
|
63
|
+
/**
|
|
64
|
+
* True when this backend serves the proof itself rather than reading it somewhere else.
|
|
65
|
+
* A hosted proof cannot go missing behind our back, so it never needs reconfirming.
|
|
66
|
+
*/
|
|
67
|
+
hosted?: boolean;
|
|
68
|
+
/** What a reader should expect to find at artifactUrl, such as a challenge token. */
|
|
69
|
+
expect?: string;
|
|
70
|
+
/**
|
|
71
|
+
* When this side was last confirmed. For `declared` and `oauth` that is the moment it
|
|
72
|
+
* was established; artifact methods drift out of date and are reconfirmed on a schedule.
|
|
73
|
+
*/
|
|
74
|
+
confirmedAt: number;
|
|
75
|
+
}
|
|
76
|
+
/** Each side of a link is attested separately, by different parties under different methods. */
|
|
77
|
+
export interface Attestations {
|
|
78
|
+
local: Attestation;
|
|
79
|
+
/**
|
|
80
|
+
* Every method the external account has been shown by, in the order each was first
|
|
81
|
+
* used, at most one per method. The first is the main one for the record's life, and a
|
|
82
|
+
* record is judged by it; the rest are additional and never replace it.
|
|
83
|
+
*/
|
|
84
|
+
external: [Attestation, ...Attestation[]];
|
|
85
|
+
}
|
|
86
|
+
export interface Connection {
|
|
87
|
+
id: string;
|
|
88
|
+
local: LocalAccount;
|
|
89
|
+
external: ExternalAccount;
|
|
90
|
+
provider: string;
|
|
91
|
+
visibility: Visibility;
|
|
92
|
+
visibilityApprovedAt: number;
|
|
93
|
+
authenticatedAt: number;
|
|
94
|
+
approvedAt: number;
|
|
95
|
+
/**
|
|
96
|
+
* When the record was first made. A renewal moves every other time on the record forward
|
|
97
|
+
* and never this one, which is what says the order a subject's accounts were connected
|
|
98
|
+
* in. Absent on records written before it was kept.
|
|
99
|
+
*/
|
|
100
|
+
connectedAt?: number;
|
|
101
|
+
expiresAt: number;
|
|
102
|
+
revokedAt?: number;
|
|
103
|
+
/** Who removed it: the local holder, the external holder, or a withdrawal the provider read. */
|
|
104
|
+
revocationReason?: 'local' | 'external' | 'withdrawn';
|
|
105
|
+
/** Absent on records written before methods were recorded; evidence infers those. */
|
|
106
|
+
attestations?: Attestations;
|
|
107
|
+
/** The proof itself, for a method whose artifact this backend publishes rather than reads. */
|
|
108
|
+
proof?: string;
|
|
109
|
+
}
|
|
110
|
+
export interface Flow {
|
|
111
|
+
id: string;
|
|
112
|
+
stateHash: string;
|
|
113
|
+
bindingHash: string;
|
|
114
|
+
verifier?: string;
|
|
115
|
+
kind: 'connect' | 'renew' | 'revoke' | 'visibility' | 'share-revoke';
|
|
116
|
+
local?: LocalAccount;
|
|
117
|
+
connectionId?: string;
|
|
118
|
+
phase: 'pending' | 'exchanging' | 'approval' | 'complete' | 'cancelled' | 'failed';
|
|
119
|
+
expiresAt: number;
|
|
120
|
+
external?: ExternalAccount;
|
|
121
|
+
authenticatedAt?: number;
|
|
122
|
+
resultId?: string;
|
|
123
|
+
/** Why a failed flow failed, when a provider said so in words meant for the holder. */
|
|
124
|
+
reason?: string;
|
|
125
|
+
/**
|
|
126
|
+
* The string an artifact must contain. Public by design: the holder publishes it. It is
|
|
127
|
+
* unguessable and per-flow, so an artifact made for one flow cannot complete another.
|
|
128
|
+
*/
|
|
129
|
+
expect?: string;
|
|
130
|
+
/** What the holder handed back: an address to read, or the proof itself. */
|
|
131
|
+
artifact?: string;
|
|
132
|
+
/**
|
|
133
|
+
* A message on its way to the address the holder named, carrying a link to press and a
|
|
134
|
+
* code to type. The account is only a claim until one of them comes back, which is why
|
|
135
|
+
* it is kept here and not in `external`. Never public: either one is the whole proof, so
|
|
136
|
+
* only their hashes are stored and none of this is reported.
|
|
137
|
+
*/
|
|
138
|
+
sent?: {
|
|
139
|
+
account: ExternalAccount;
|
|
140
|
+
codeHash: string;
|
|
141
|
+
linkHash: string;
|
|
142
|
+
attempts: number;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Which configured method the flow runs: the provider's id and its method. Absent on
|
|
146
|
+
* flows written by a backend that had only one, which is the first one configured.
|
|
147
|
+
*/
|
|
148
|
+
provider?: string;
|
|
149
|
+
method?: Method;
|
|
150
|
+
/**
|
|
151
|
+
* What the adopting application attached when the flow was created, such as which site
|
|
152
|
+
* sent the holder here. Never set on removal from the external side, which is started by
|
|
153
|
+
* nobody local whatever the browser is signed into.
|
|
154
|
+
*/
|
|
155
|
+
context?: Record<string, string>;
|
|
156
|
+
/** How the flow ended, written in the transaction that ended it and never changed after. */
|
|
157
|
+
result?: FlowResult;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The end of a flow as it stood at that moment. The connection may change later, so anything
|
|
161
|
+
* reporting the result reads it from here and never from the connection.
|
|
162
|
+
*/
|
|
163
|
+
export interface FlowResult {
|
|
164
|
+
kind: Flow['kind'];
|
|
165
|
+
phase: 'complete' | 'cancelled' | 'failed';
|
|
166
|
+
/** The connection the flow made or acted on, where there is one. */
|
|
167
|
+
connectionId?: string;
|
|
168
|
+
/** That connection's visibility when the flow ended. */
|
|
169
|
+
visibility?: Visibility;
|
|
170
|
+
finishedAt: number;
|
|
171
|
+
}
|
|
172
|
+
export interface Share {
|
|
173
|
+
connectionId: string;
|
|
174
|
+
tokenHash: string;
|
|
175
|
+
createdAt: number;
|
|
176
|
+
expiresAt: number;
|
|
177
|
+
revokedAt?: number;
|
|
178
|
+
}
|
|
179
|
+
export interface Audit {
|
|
180
|
+
id: string;
|
|
181
|
+
connectionId?: string;
|
|
182
|
+
flowId?: string;
|
|
183
|
+
action: string;
|
|
184
|
+
actor: 'local' | 'external';
|
|
185
|
+
at: number;
|
|
186
|
+
visibility?: Visibility;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* How many times something has been done in the window that ends at `expiresAt`. Past
|
|
190
|
+
* that the count is spent and the next one starts a window of its own.
|
|
191
|
+
*/
|
|
192
|
+
export interface Limit {
|
|
193
|
+
id: string;
|
|
194
|
+
count: number;
|
|
195
|
+
expiresAt: number;
|
|
196
|
+
}
|
|
197
|
+
export interface Records {
|
|
198
|
+
connections: Connection;
|
|
199
|
+
flows: Flow;
|
|
200
|
+
shares: Share;
|
|
201
|
+
audit: Audit;
|
|
202
|
+
limits: Limit;
|
|
203
|
+
}
|
|
204
|
+
export interface Transaction {
|
|
205
|
+
get<K extends keyof Records>(kind: K, id: string): Promise<Records[K] | undefined>;
|
|
206
|
+
put<K extends keyof Records>(kind: K, id: string, value: Records[K]): Promise<void>;
|
|
207
|
+
delete(kind: keyof Records, id: string): Promise<void>;
|
|
208
|
+
list<K extends keyof Records>(kind: K): Promise<Records[K][]>;
|
|
209
|
+
}
|
|
210
|
+
/** Must serialize concurrent transactions and roll back all writes on rejection. */
|
|
211
|
+
export interface Storage {
|
|
212
|
+
transaction<T>(work: (tx: Transaction) => Promise<T>): Promise<T>;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Proves control by sending the holder to the provider and back in one round trip. The
|
|
216
|
+
* provider is the only party that sees the holder's credentials, and nothing is published.
|
|
217
|
+
*/
|
|
218
|
+
export interface RedirectProvider {
|
|
219
|
+
id: string;
|
|
220
|
+
name: string;
|
|
221
|
+
/** Absent means oauth, the shape this interface describes. */
|
|
222
|
+
method?: 'oauth';
|
|
223
|
+
authorizationUrl(input: {
|
|
224
|
+
state: string;
|
|
225
|
+
challenge: string;
|
|
226
|
+
redirectUri: string;
|
|
227
|
+
}): string;
|
|
228
|
+
authenticate(input: {
|
|
229
|
+
code: string;
|
|
230
|
+
verifier: string;
|
|
231
|
+
redirectUri: string;
|
|
232
|
+
}): Promise<ExternalAccount>;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Proves control by having the holder publish a given string somewhere only they can write,
|
|
236
|
+
* then reading it back. It is holder-paced rather than one round trip, it needs no
|
|
237
|
+
* registration with the provider, and it leaves a proof a reader can check for themselves.
|
|
238
|
+
*/
|
|
239
|
+
export interface ArtifactProvider {
|
|
240
|
+
id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
method: Exclude<Method, 'declared' | 'oauth'>;
|
|
243
|
+
/**
|
|
244
|
+
* What the holder must publish, for a method that does not get to choose it. A backlink
|
|
245
|
+
* says the far side points at this exact subject, so the subject's own address is the
|
|
246
|
+
* whole of what is proved and a per-flow token could not be part of it. Absent means the
|
|
247
|
+
* backend mints an unguessable string instead, which is what a method wants when any
|
|
248
|
+
* fresh artifact will do. Throw when the subject cannot be proved this way at all.
|
|
249
|
+
*/
|
|
250
|
+
expect?(local: LocalAccount): string;
|
|
251
|
+
/**
|
|
252
|
+
* Where the proof lives. `location` means the holder publishes it somewhere only they
|
|
253
|
+
* can write and hands back an address, so the address is half of what is proved.
|
|
254
|
+
* `document` means they hand back the proof itself and this backend publishes it,
|
|
255
|
+
* because a signature proves itself and where it was typed proves nothing at all.
|
|
256
|
+
*/
|
|
257
|
+
artifact: 'location' | 'document';
|
|
258
|
+
/**
|
|
259
|
+
* Told to the holder verbatim. Must name what they are publishing and where. Returned in
|
|
260
|
+
* pieces so a renderer can set a command as one: a command is copied character for
|
|
261
|
+
* character, and prose that reflows is prose a holder cannot safely copy.
|
|
262
|
+
*/
|
|
263
|
+
instructions(expect: string): Instruction[];
|
|
264
|
+
/**
|
|
265
|
+
* Reads what the holder handed back and returns whose it is. Must confirm it contains
|
|
266
|
+
* `expect`. A `location` provider must also refuse any address outside itself, since
|
|
267
|
+
* the holder chooses that address and an unpinned fetch is an open proxy. Throw `Refused`
|
|
268
|
+
* to tell the holder why; any other error is shown only as a failure.
|
|
269
|
+
*/
|
|
270
|
+
verify(input: {
|
|
271
|
+
artifact: string;
|
|
272
|
+
expect: string;
|
|
273
|
+
}): Promise<ExternalAccount>;
|
|
274
|
+
/**
|
|
275
|
+
* Whether the holder has since withdrawn the identity itself, wherever such a statement
|
|
276
|
+
* is published. This revokes the connection, so it must be something the holder stated
|
|
277
|
+
* and this provider verified, never something a third party merely asserted.
|
|
278
|
+
*/
|
|
279
|
+
withdrawn?(account: ExternalAccount, artifact: string): Promise<boolean>;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Proves control by sending a message somewhere only the holder can read and having them
|
|
283
|
+
* answer it: by pressing the link it carries, or by reading its code back. It is
|
|
284
|
+
* holder-paced like an artifact, but nothing is published: the proof is that the message
|
|
285
|
+
* arrived, so like a sign-in it happened once and there is nothing for a reader to fetch
|
|
286
|
+
* or for a recheck to read again.
|
|
287
|
+
*
|
|
288
|
+
* The backend mints the link and the code, counts the guesses and never hands the
|
|
289
|
+
* provider anything else to say, so a provider cannot be made to carry a stranger's words
|
|
290
|
+
* to an address a stranger chose.
|
|
291
|
+
*/
|
|
292
|
+
export interface CodeProvider {
|
|
293
|
+
id: string;
|
|
294
|
+
name: string;
|
|
295
|
+
method: 'code';
|
|
296
|
+
/** What the holder is asked for, such as "Your email address". */
|
|
297
|
+
field: string;
|
|
298
|
+
/** The kind of address, which says what a browser offers to fill in for it. */
|
|
299
|
+
input: 'email' | 'tel';
|
|
300
|
+
/**
|
|
301
|
+
* Whose account an address names, in the one spelling this provider keeps for it.
|
|
302
|
+
* Called before anything is sent. Throw `Refused` for an address it will not send to.
|
|
303
|
+
*/
|
|
304
|
+
account(address: string): ExternalAccount;
|
|
305
|
+
/** Sends the message. Rejecting fails the flow, and only a `Refused` reason is shown. */
|
|
306
|
+
deliver(input: {
|
|
307
|
+
account: ExternalAccount;
|
|
308
|
+
/**
|
|
309
|
+
* A page on this backend that confirms the address once its holder presses the button
|
|
310
|
+
* there. Following the link alone confirms nothing, so a scanner that opens every link
|
|
311
|
+
* in a message cannot answer for the holder. Works from any browser.
|
|
312
|
+
*/
|
|
313
|
+
link: string;
|
|
314
|
+
/** The same proof as something to type, into the page that asked. */
|
|
315
|
+
code: string;
|
|
316
|
+
/**
|
|
317
|
+
* Who the message says is asking: the site the holder is linking this account to, or,
|
|
318
|
+
* for a flow anybody may start on somebody else's record, only this installation.
|
|
319
|
+
*/
|
|
320
|
+
siteName: string;
|
|
321
|
+
/** When both stop working. */
|
|
322
|
+
expiresAt: number;
|
|
323
|
+
}): Promise<void>;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* A proof turned down for a reason the holder can act on. Its message is shown to them, so
|
|
327
|
+
* it names what was wrong with what they handed over and nothing about the backend.
|
|
328
|
+
*/
|
|
329
|
+
export declare class Refused extends Error {
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* A piece of what the holder is told: a paragraph, a paragraph with links in it, or
|
|
333
|
+
* something they run or publish as is.
|
|
334
|
+
*/
|
|
335
|
+
export type Instruction = string | {
|
|
336
|
+
code: string;
|
|
337
|
+
} | Inline[];
|
|
338
|
+
/**
|
|
339
|
+
* A piece of a paragraph: text, or text that links somewhere the holder goes to do what
|
|
340
|
+
* the paragraph says. Renderers link only http(s) addresses and open them in a new tab.
|
|
341
|
+
*/
|
|
342
|
+
export type Inline = string | {
|
|
343
|
+
text: string;
|
|
344
|
+
href: string;
|
|
345
|
+
};
|
|
346
|
+
export type Provider = RedirectProvider | ArtifactProvider | CodeProvider;
|
|
347
|
+
/** Only an artifact provider is holder-paced, and only it needs a url handed back. */
|
|
348
|
+
export declare function isArtifactProvider(provider: Provider): provider is ArtifactProvider;
|
|
349
|
+
/** The one shape that sends the holder to the provider's own site and takes them back. */
|
|
350
|
+
export declare function isRedirectProvider(provider: Provider): provider is RedirectProvider;
|
|
351
|
+
/** Only a code provider sends the holder something to read back. */
|
|
352
|
+
export declare function isCodeProvider(provider: Provider): provider is CodeProvider;
|
|
353
|
+
/** How a provider shows control. A redirect provider that names none is oauth. */
|
|
354
|
+
export declare function providerMethod(provider: Provider): Method;
|
|
355
|
+
export interface Evidence {
|
|
356
|
+
id: string;
|
|
357
|
+
/** The site's name is `siteName` below, so the subject does not carry a second copy. */
|
|
358
|
+
local: Omit<LocalAccount, 'id' | 'siteName'>;
|
|
359
|
+
external: ExternalAccount;
|
|
360
|
+
provider: string;
|
|
361
|
+
/** Display name from the provider implementation. */
|
|
362
|
+
providerName: string;
|
|
363
|
+
/**
|
|
364
|
+
* How each side was established, so a reader can weigh them separately instead of
|
|
365
|
+
* reading one undifferentiated "verified".
|
|
366
|
+
*/
|
|
367
|
+
attestations: Attestations;
|
|
368
|
+
siteName: string;
|
|
369
|
+
verifierName: string;
|
|
370
|
+
visibility: Visibility;
|
|
371
|
+
status: Status;
|
|
372
|
+
/**
|
|
373
|
+
* When the record was first made, which no renewal changes. A subject's accounts are
|
|
374
|
+
* shown in this order, earliest first. For a record older than this field it is the
|
|
375
|
+
* earliest time the record still carries.
|
|
376
|
+
*/
|
|
377
|
+
connectedAt: number;
|
|
378
|
+
authenticatedAt: number;
|
|
379
|
+
approvedAt: number;
|
|
380
|
+
visibilityApprovedAt: number;
|
|
381
|
+
expiresAt: number;
|
|
382
|
+
revokedAt?: number;
|
|
383
|
+
evidenceUrl: string;
|
|
384
|
+
/**
|
|
385
|
+
* Where this record can be had signed, on a verifier that signs. Only a public record
|
|
386
|
+
* that stands has a signed form: once saved, a signed record cannot be taken back.
|
|
387
|
+
*/
|
|
388
|
+
signedUrl?: string;
|
|
389
|
+
}
|
|
390
|
+
export * from './signed.js';
|
|
391
|
+
/**
|
|
392
|
+
* How long a published proof stays good without being read again. An artifact method is
|
|
393
|
+
* only true while the artifact is still there, and the holder can delete it without
|
|
394
|
+
* telling anyone, so a confirmation is a heartbeat rather than a permanent fact.
|
|
395
|
+
*/
|
|
396
|
+
export declare const freshnessMs: number;
|
|
397
|
+
/**
|
|
398
|
+
* `freshness` bounds how stale a published proof may be before it stops counting. A
|
|
399
|
+
* backend that never rechecks must leave it unbounded, since an unread proof going stale
|
|
400
|
+
* is a statement about the recheck, and claiming one that never ran would be a lie.
|
|
401
|
+
*/
|
|
402
|
+
export declare function status(connection: Connection, now: number, freshness?: number): Status;
|
|
403
|
+
/**
|
|
404
|
+
* Whether one method still counts. A sign-in happened once and stays happened. A proof
|
|
405
|
+
* that has not been read lately is unconfirmed rather than disproved, which is why this
|
|
406
|
+
* reverses the moment it reads again.
|
|
407
|
+
*/
|
|
408
|
+
export declare function fresh(attestation: Attestation, now: number, freshness?: number): boolean;
|
|
409
|
+
/**
|
|
410
|
+
* The word for a record's state. Evidence can be read after it was issued, so an approval
|
|
411
|
+
* that has run out since reads as expired whatever status it was issued with.
|
|
412
|
+
*/
|
|
413
|
+
export declare function statusLabel(evidence: Pick<Evidence, 'status' | 'expiresAt'>, now: number): string;
|
|
414
|
+
/**
|
|
415
|
+
* What to call a link's local side, and what to write as its value. When the site itself is
|
|
416
|
+
* what was linked there is no subject on it to name, so the site is the value rather than a
|
|
417
|
+
* label above some other thing. An absent kind means the site did not say, so nothing is
|
|
418
|
+
* assumed about one. A subject that names its own site is described by that name, and
|
|
419
|
+
* `siteName` is only the fallback.
|
|
420
|
+
*/
|
|
421
|
+
export declare function localSide(local: Pick<LocalAccount, 'kind' | 'label' | 'siteName'>, fallback: string): {
|
|
422
|
+
heading: string;
|
|
423
|
+
value: string;
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* How to write an external subject's name. The @ that marks a handle is a claim that there
|
|
427
|
+
* is an account behind it, issued by somebody who could also take it away. A key has no
|
|
428
|
+
* account and no handle: it is named by its own fingerprint, so it is written as it is. A
|
|
429
|
+
* page is named by its address for the same reason, since fetching one says where it is
|
|
430
|
+
* and never who holds it. A mailbox's address already carries its own @.
|
|
431
|
+
*/
|
|
432
|
+
export declare function externalName(external: ExternalAccount): string;
|
|
433
|
+
/**
|
|
434
|
+
* How to describe one side's proof in plain words. It names the attester and the method
|
|
435
|
+
* together and does not rank them: whether a given proof is convincing is the reader's
|
|
436
|
+
* judgement, which is the reason for publishing it rather than a verdict about it.
|
|
437
|
+
*
|
|
438
|
+
* An unrecognised method returns nothing, so a renderer omits the line instead of
|
|
439
|
+
* describing a proof it does not understand.
|
|
440
|
+
*/
|
|
441
|
+
export declare function attestationLabel(method: string, names: {
|
|
442
|
+
site: string;
|
|
443
|
+
provider: string;
|
|
444
|
+
}): string | undefined;
|
|
445
|
+
/**
|
|
446
|
+
* Where a subject's name may link, or nothing where it has no page of its own. A mailbox's
|
|
447
|
+
* address is `mailto:`, which opens a message to it and shows a reader nothing.
|
|
448
|
+
*/
|
|
449
|
+
export declare function externalLink(external: ExternalAccount): string | undefined;
|
|
450
|
+
/**
|
|
451
|
+
* The hover text on a proof link: where it leads, then when it was last read there. A
|
|
452
|
+
* proof this backend serves itself is never reread, so it gives no such time.
|
|
453
|
+
*/
|
|
454
|
+
export declare function proofTitle(attestation: Attestation, when: (time: number) => string): string;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A proof turned down for a reason the holder can act on. Its message is shown to them, so
|
|
3
|
+
* it names what was wrong with what they handed over and nothing about the backend.
|
|
4
|
+
*/
|
|
5
|
+
export class Refused extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** Only an artifact provider is holder-paced, and only it needs a url handed back. */
|
|
8
|
+
export function isArtifactProvider(provider) {
|
|
9
|
+
return 'verify' in provider;
|
|
10
|
+
}
|
|
11
|
+
/** The one shape that sends the holder to the provider's own site and takes them back. */
|
|
12
|
+
export function isRedirectProvider(provider) {
|
|
13
|
+
return 'authenticate' in provider;
|
|
14
|
+
}
|
|
15
|
+
/** Only a code provider sends the holder something to read back. */
|
|
16
|
+
export function isCodeProvider(provider) {
|
|
17
|
+
return 'deliver' in provider;
|
|
18
|
+
}
|
|
19
|
+
/** How a provider shows control. A redirect provider that names none is oauth. */
|
|
20
|
+
export function providerMethod(provider) {
|
|
21
|
+
return provider.method ?? 'oauth';
|
|
22
|
+
}
|
|
23
|
+
export * from './signed.js';
|
|
24
|
+
/**
|
|
25
|
+
* How long a published proof stays good without being read again. An artifact method is
|
|
26
|
+
* only true while the artifact is still there, and the holder can delete it without
|
|
27
|
+
* telling anyone, so a confirmation is a heartbeat rather than a permanent fact.
|
|
28
|
+
*/
|
|
29
|
+
export const freshnessMs = 7 * 86400000;
|
|
30
|
+
/**
|
|
31
|
+
* `freshness` bounds how stale a published proof may be before it stops counting. A
|
|
32
|
+
* backend that never rechecks must leave it unbounded, since an unread proof going stale
|
|
33
|
+
* is a statement about the recheck, and claiming one that never ran would be a lie.
|
|
34
|
+
*/
|
|
35
|
+
export function status(connection, now, freshness = freshnessMs) {
|
|
36
|
+
if (connection.revokedAt !== undefined)
|
|
37
|
+
return 'revoked';
|
|
38
|
+
if (now >= connection.expiresAt)
|
|
39
|
+
return 'expired';
|
|
40
|
+
const main = connection.attestations?.external[0];
|
|
41
|
+
if (main && !fresh(main, now, freshness))
|
|
42
|
+
return 'unconfirmed';
|
|
43
|
+
return 'verified';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether one method still counts. A sign-in happened once and stays happened. A proof
|
|
47
|
+
* that has not been read lately is unconfirmed rather than disproved, which is why this
|
|
48
|
+
* reverses the moment it reads again.
|
|
49
|
+
*/
|
|
50
|
+
export function fresh(attestation, now, freshness = freshnessMs) {
|
|
51
|
+
return (!attestation.artifactUrl ||
|
|
52
|
+
attestation.hosted === true ||
|
|
53
|
+
freshness === Infinity ||
|
|
54
|
+
now < attestation.confirmedAt + freshness);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The word for a record's state. Evidence can be read after it was issued, so an approval
|
|
58
|
+
* that has run out since reads as expired whatever status it was issued with.
|
|
59
|
+
*/
|
|
60
|
+
export function statusLabel(evidence, now) {
|
|
61
|
+
if (evidence.status === 'revoked')
|
|
62
|
+
return 'Revoked';
|
|
63
|
+
if (evidence.status === 'expired' || evidence.expiresAt <= now)
|
|
64
|
+
return 'Expired';
|
|
65
|
+
return evidence.status === 'unconfirmed' ? 'Unconfirmed' : 'Verified';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* What to call a link's local side, and what to write as its value. When the site itself is
|
|
69
|
+
* what was linked there is no subject on it to name, so the site is the value rather than a
|
|
70
|
+
* label above some other thing. An absent kind means the site did not say, so nothing is
|
|
71
|
+
* assumed about one. A subject that names its own site is described by that name, and
|
|
72
|
+
* `siteName` is only the fallback.
|
|
73
|
+
*/
|
|
74
|
+
export function localSide(local, fallback) {
|
|
75
|
+
const siteName = local.siteName ?? fallback;
|
|
76
|
+
if (local.kind === 'site')
|
|
77
|
+
return { heading: 'Website', value: siteName };
|
|
78
|
+
const heading = {
|
|
79
|
+
account: `Account on ${siteName}`,
|
|
80
|
+
page: `Page on ${siteName}`,
|
|
81
|
+
};
|
|
82
|
+
return { heading: heading[local.kind ?? ''] ?? siteName, value: local.label };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* How to write an external subject's name. The @ that marks a handle is a claim that there
|
|
86
|
+
* is an account behind it, issued by somebody who could also take it away. A key has no
|
|
87
|
+
* account and no handle: it is named by its own fingerprint, so it is written as it is. A
|
|
88
|
+
* page is named by its address for the same reason, since fetching one says where it is
|
|
89
|
+
* and never who holds it. A mailbox's address already carries its own @.
|
|
90
|
+
*/
|
|
91
|
+
export function externalName(external) {
|
|
92
|
+
return external.kind === undefined || external.kind === 'account'
|
|
93
|
+
? `@${external.handle.replace(/^@/, '')}`
|
|
94
|
+
: external.handle;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* How to describe one side's proof in plain words. It names the attester and the method
|
|
98
|
+
* together and does not rank them: whether a given proof is convincing is the reader's
|
|
99
|
+
* judgement, which is the reason for publishing it rather than a verdict about it.
|
|
100
|
+
*
|
|
101
|
+
* An unrecognised method returns nothing, so a renderer omits the line instead of
|
|
102
|
+
* describing a proof it does not understand.
|
|
103
|
+
*/
|
|
104
|
+
export function attestationLabel(method, names) {
|
|
105
|
+
return {
|
|
106
|
+
declared: `Stated by ${names.site}`,
|
|
107
|
+
oauth: `Signed in with ${names.provider}`,
|
|
108
|
+
gist: `Published a proof on ${names.provider}`,
|
|
109
|
+
backlink: `Linked back to ${names.site}`,
|
|
110
|
+
signature: 'Proved with a signature',
|
|
111
|
+
code: 'Entered a code sent to this address',
|
|
112
|
+
}[method];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Where a subject's name may link, or nothing where it has no page of its own. A mailbox's
|
|
116
|
+
* address is `mailto:`, which opens a message to it and shows a reader nothing.
|
|
117
|
+
*/
|
|
118
|
+
export function externalLink(external) {
|
|
119
|
+
return external.kind === 'mailbox' ? undefined : external.profileUrl;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The hover text on a proof link: where it leads, then when it was last read there. A
|
|
123
|
+
* proof this backend serves itself is never reread, so it gives no such time.
|
|
124
|
+
*/
|
|
125
|
+
export function proofTitle(attestation, when) {
|
|
126
|
+
const where = `View the proof at ${new URL(attestation.artifactUrl).host}`;
|
|
127
|
+
return attestation.hosted ? where : `${where} | Last checked ${when(attestation.confirmedAt)}`;
|
|
128
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Evidence } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A verifier's public key, as it publishes it: an OpenPGP key, named by its fingerprint.
|
|
4
|
+
* `alg` says how a record under it is read, so another scheme can sit beside this one.
|
|
5
|
+
*/
|
|
6
|
+
export interface VerifierKey {
|
|
7
|
+
/** The key's fingerprint, in the upper case it is read aloud in. */
|
|
8
|
+
id: string;
|
|
9
|
+
alg: 'OpenPGP';
|
|
10
|
+
/** The armored public key, as `gpg --import` reads it. */
|
|
11
|
+
publicKey: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What a signature covers: the record as it stood at `issuedAt`. Whether it still stands
|
|
15
|
+
* is a live fact no signature holds, so its status is left out and `evidenceUrl` is where
|
|
16
|
+
* to ask.
|
|
17
|
+
*/
|
|
18
|
+
export type SignedDocument = Omit<Evidence, 'status' | 'revokedAt' | 'signedUrl'> & {
|
|
19
|
+
type: 'verily-evidence';
|
|
20
|
+
version: 1;
|
|
21
|
+
issuedAt: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A record that can be checked away from the verifier that served it: the document as
|
|
25
|
+
* text under an OpenPGP cleartext signature, which `gpg --verify` reads as it is.
|
|
26
|
+
*/
|
|
27
|
+
export type SignedEvidence = string;
|
|
28
|
+
export interface Signer {
|
|
29
|
+
key: VerifierKey;
|
|
30
|
+
sign(document: SignedDocument): Promise<SignedEvidence>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The fingerprint of the key a signed record says signed it, checked or not: which key to
|
|
34
|
+
* look for, and no reason by itself to believe anything.
|
|
35
|
+
*/
|
|
36
|
+
export declare function signedBy(signed: unknown): Promise<string | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* The document a signed record carries, if one of the keys given signed it, and nothing
|
|
39
|
+
* otherwise. The keys are the caller's to choose: a record is only as good as the reason
|
|
40
|
+
* to believe each key is the verifier's. It says what stood when it was signed, never
|
|
41
|
+
* that it stands now.
|
|
42
|
+
*
|
|
43
|
+
* Whether a signature counts is OpenPGP.js's to say, by the format's own rules: a key is
|
|
44
|
+
* judged as it stood when the record was signed, so one that has since run out, or been
|
|
45
|
+
* retired, still answers for what it signed before, and one revoked as compromised signs
|
|
46
|
+
* nothing at all. Only a record signed by the listed key itself is taken, not by a subkey
|
|
47
|
+
* of it, so the fingerprint a record names is the one a list holds.
|
|
48
|
+
*/
|
|
49
|
+
export declare function verifySigned(signed: unknown, keys: VerifierKey[]): Promise<SignedDocument | undefined>;
|